From 165f6320a6f97eb76c136ecd04080dfb16272eb5 Mon Sep 17 00:00:00 2001 From: John Konderla Date: Tue, 27 Oct 2020 18:36:39 -0400 Subject: [PATCH] Refactor bin/kamstrup_prober (#517) * refactored for readability * added new def layout to reduce complexity --- bin/kamstrup_prober.py | 164 ++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 75 deletions(-) diff --git a/bin/kamstrup_prober.py b/bin/kamstrup_prober.py index c9d5f8e4..836870ab 100644 --- a/bin/kamstrup_prober.py +++ b/bin/kamstrup_prober.py @@ -15,6 +15,7 @@ # Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import logging import socket import json from datetime import datetime @@ -25,6 +26,12 @@ import xml.dom.minidom from conpot.protocols.kamstrup.meter_protocol import kamstrup_constants +logger = logging.getLogger(__name__) + +port_start_range = 1 +port_end_range = 65535 +default_comm_port = 63 + class KamstrupRegisterCopier(object): def __init__(self, ip_address, port, comm_address): @@ -35,7 +42,7 @@ def __init__(self, ip_address, port, comm_address): self._connect() def _connect(self): - print("Connecting to {0}:{1}".format(self.ip_address, self.port)) + logger.info("Connecting to {0}:{1}".format(self.ip_address, self.port)) if self._sock is not None: self._sock.close() time.sleep(1) @@ -44,7 +51,7 @@ def _connect(self): try: self._sock.connect((self.ip_address, self.port)) except socket.error as socket_err: - print("Error while connecting: {0}".format(str(socket_err))) + logger.exception("Error while connecting: {0}".format(str(socket_err))) self._connect() def get_register(self, register): @@ -77,7 +84,7 @@ def get_register(self, register): received_data = self._sock.recv(1024) received_data = bytearray(received_data) except socket.error as socket_err: - print("Error while communicating: {0}".format(str(socket_err))) + logger.exception("Error while communicating: {0}".format(str(socket_err))) self._connect() data_length = len(received_data) @@ -93,74 +100,71 @@ def get_register(self, register): return received_data -def json_default(obj): - if isinstance(obj, datetime): - return obj.isoformat() +def find_registers_in_candidates(args): + if args.registerfile: + candidate_registers_values = [] + with open(args.registerfile, "r") as register_file: + old_register_values = json.load(register_file) + for value in old_register_values.iterkeys(): + candidate_registers_values.append(int(value)) else: - return None + candidate_registers_values = range(port_start_range, port_end_range) + found_registers = registers_from_candidates(candidate_registers_values, args) -parser = argparse.ArgumentParser(description="Probes kamstrup_meter meter registers.") -parser.add_argument("host", help="Hostname or IP or Kamstrup meter") -parser.add_argument("port", type=int, help="TCP port") -parser.add_argument( - "--registerfile", - dest="registerfile", - help="Reads registers from previous dumps files instead of" - "bruteforcing the meter.", -) -parser.add_argument("--comaddress", dest="communication_address", default=0x3F) -args = parser.parse_args() - -if args.registerfile: - candidate_registers_values = [] - with open(args.registerfile, "r") as register_file: - old_register_values = json.load(register_file) - for value in old_register_values.iterkeys(): - candidate_registers_values.append(int(value)) -else: - candidate_registers_values = range(0x01, 0xFFFF) - -kamstrupRegisterCopier = KamstrupRegisterCopier( - args.host, args.port, int(args.communication_address) -) -found_registers = {} -not_found_counts = 0 -scanned = 0 -dumpfile = "kamstrup_dump_{0}.json".format( - calendar.timegm(datetime.utcnow().utctimetuple()) -) - -for x in candidate_registers_values: - result = kamstrupRegisterCopier.get_register(x) - if len(result) > 12: - units = result[5] - length = result[6] - unknown = result[7] - - register_value = 0 - for p in range(length): - register_value += result[8 + p] << (8 * ((length - p) - 1)) - - found_registers[x] = { - "timestamp": datetime.utcnow(), - "units": units, - "value": register_value, - "value_length": length, - "unknown": unknown, - } - print("Found register value at {0}:{1}".format(hex(x), register_value)) - with open(dumpfile, "w") as json_file: - json_file.write(json.dumps(found_registers, indent=4, default=json_default)) - else: - not_found_counts += 1 - if not_found_counts % 10 == 0: - print( - "Hang on, still scanning, so far scanned {0} and found {1} registers".format( - scanned, len(found_registers) + logger.info( + "Scanned {0} registers, found {1}.".format( + len(candidate_registers_values), len(found_registers) + ) + ) + # with open('kamstrup_dump_{0}.json'.format(calendar.timegm(datetime.utcnow().utctimetuple())), 'w') as json_file: + # json_file.write(json.dumps(found_registers, indent=4, default=json_default)) + logger.info("""*** Sample Conpot configuration from this scrape:""") + logger.info(generate_conpot_config(found_registers)) + + +def registers_from_candidates(candidate_registers_values, args): + kamstrupRegisterCopier = KamstrupRegisterCopier( + args.host, args.port, int(args.communication_address) + ) + found_registers = {} + not_found_counts = 0 + scanned = 0 + dumpfile = "kamstrup_dump_{0}.json".format( + calendar.timegm(datetime.utcnow().utctimetuple()) + ) + for register_id in candidate_registers_values: + result = kamstrupRegisterCopier.get_register(register_id) + if len(result) > 12: + units = result[5] + length = result[6] + unknown = result[7] + + register_value = 0 + for p in range(length): + register_value += result[8 + p] << (8 * ((length - p) - 1)) + + found_registers[register_id] = { + "timestamp": datetime.utcnow(), + "units": units, + "value": register_value, + "value_length": length, + "unknown": unknown, + } + logger.info("Found register value at {0}:{1}".format(hex(register_id), register_value)) + with open(dumpfile, "w") as json_file: + json_file.write(json.dumps(found_registers, indent=4, default=json_default)) + else: + not_found_counts += 1 + if not_found_counts % 10 == 0: + logger.info( + "Hang on, still scanning, so far scanned {0} and found {1} registers".format( + scanned, len(found_registers) + ) ) - ) - scanned += 1 + scanned += 1 + + return found_registers def generate_conpot_config(result_list): @@ -185,12 +189,22 @@ def generate_conpot_config(result_list): return pretty_xml -print( - "Scanned {0} registers, found {1}.".format( - len(candidate_registers_values), len(found_registers) +def json_default(obj): + if isinstance(obj, datetime): + return obj.isoformat() + else: + return None + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Probes kamstrup_meter meter registers.") + parser.add_argument("host", help="Hostname or IP or Kamstrup meter") + parser.add_argument("port", type=int, help="TCP port") + parser.add_argument( + "--registerfile", + dest="registerfile", + help="Reads registers from previous dumps files instead of bruteforcing the meter.", ) -) -# with open('kamstrup_dump_{0}.json'.format(calendar.timegm(datetime.utcnow().utctimetuple())), 'w') as json_file: -# json_file.write(json.dumps(found_registers, indent=4, default=json_default)) -print("""*** Sample Conpot configuration from this scrape:""") -print(generate_conpot_config(found_registers)) + parser.add_argument("--comm-addr", dest="communication_address", default=default_comm_port) + + find_registers_in_candidates(parser.parse_args())