Пример #1
0
 def __init__(self,
              client_ip: str,
              sniff_interface: str,
              stratum_file_path: str = '/etc/stratum_override.txt',
              num_pck_per_bit=1,
              log: logging.Logger = logging.getLogger('CP2Server-logging')):
     """
     A CP2 Server that listens for incoming NTP requests and injects the manipulated stratum value to a config
     file on the harddrive
     :param log:
     """
     self.log = log
     self.stratum_file_path = stratum_file_path
     self.num_pck_per_bit = num_pck_per_bit
     self.client_ip = client_ip
     self.scapy_wrapper = ScapyWrapper()
     self.sniff_interface = sniff_interface
     self.payload_to_send = 'Hello World'
     self.payload_pointer = -1
     self.payload_bits = ''
     self.current_pck_num = 0
     self.current_stratum = None
     self.zero_mode = False
     self.init_done = False
     self.init_counter = 0
Пример #2
0
 def __init__(self,
              static_key_bits: str,
              log: logging.Logger = logging.getLogger('CP3Handler-logger')):
     """
     The core of every CP3 server and client. Provides functions to work with packages and process them.
     :param static_key_bits:
     :param log:
     """
     self.log = log
     self.static_key_bits = static_key_bits
     self.msg: str = ''
     self.scappy_wrapper = ScapyWrapper()
Пример #3
0
 def __init__(self,
              address: str,
              static_key: str,
              sniff_interface: str = 'lo',
              self_ip_addr='192.168.0.1',
              payload: str = PAYLOAD_BITS_120,
              client_address: str = ADDR_2,
              log=logging.getLogger('CP1Interceptor-Logger')):
     super().__init__(address, static_key, sniff_interface, log)
     self.scapy_wrapper = ScapyWrapper()
     self.crypto_tools = NTPCrypto()
     self.self_ip_addr = self_ip_addr
     self.payload = payload
     self.client_address = client_address
Пример #4
0
 def __init__(self,
              cp1_address_bits: str,
              static_decryption_key_bits: str,
              sniff_interface: str = 'lo',
              self_ip_addr: str = None,
              log: logging.Logger = logging.getLogger('CP1Server-logger')):
     self.log = log
     self._address = cp1_address_bits
     self.static_key_bits = static_decryption_key_bits
     self.init_pck_field = NTPField.TRANSMIT_TIMESTAMP
     self.sniff_interface = sniff_interface
     self.listen_session: CP1ClientSession = None
     self._listen_lock = Lock()
     self._release_listen = False
     self.self_ip_addr = self_ip_addr
     self.scapy_wrapper = ScapyWrapper()
     self.crypto_tools = NTPCrypto()
Пример #5
0
class CP3Handler:
    def __init__(self,
                 static_key_bits: str,
                 log: logging.Logger = logging.getLogger('CP3Handler-logger')):
        """
        The core of every CP3 server and client. Provides functions to work with packages and process them.
        :param static_key_bits:
        :param log:
        """
        self.log = log
        self.static_key_bits = static_key_bits
        self.msg: str = ''
        self.scappy_wrapper = ScapyWrapper()

    def read_incoming_pck(self, pck: CP3Package) -> bool:
        mode = pck.get_cp3_mode()
        if mode is CP3Mode.PCK_1:
            self.msg = pck.extract_payload()
            self.log.debug("CP3_Mode_1 package received and payload set: " +
                           self.msg)
            return True
        elif mode is CP3Mode.PCK_2:
            self.msg += pck.extract_payload()
            self.log.debug(
                "CP3_Mode_1 package received and complete payload now: " +
                self.msg)
            decrypted_bytes = decrypt_bits(
                self.msg,
                BitArray(bin=self.static_key_bits).bytes)
            self.log.info("Decrypted payload: " + str(decrypted_bytes))
            return True
        else:
            self.log.debug("Package had no corresponding CP3 mode")
            return False

    def create_cp3_pck(self, payload_bits: str, cp3_mode: CP3Mode,
                       ntp_mode: NTPMode) -> NTP:
        assert len(payload_bits) == 64

        pck = CP3Package()
        pck.add_payload(payload_bits)

        if cp3_mode is CP3Mode.PCK_1:
            pck.set_cp3_mode_1()
        elif cp3_mode is CP3Mode.PCK_2:
            pck.set_cp3_mode_2()
        pck.set_mode(NTPMode.to_bit_string(ntp_mode))

        return pck.ntp()

    def _send_cp3_pck(self, payload_bits: str, ip_addr: str, cp3_mode: CP3Mode,
                      ntp_mode: NTPMode):
        ntp = self.create_cp3_pck(payload_bits, cp3_mode, ntp_mode)
        self.log.debug("Sending of NTP Mode " + str(cp3_mode) +
                       " package with payload: " + payload_bits + " to " +
                       str(ip_addr))
        self.scappy_wrapper.send(IP(dst=ip_addr) / UDP() / ntp)

    def send_pck_1(self,
                   payload_bits: str,
                   ip_addr: str,
                   ntp_mode: NTPMode = NTPMode.CLIENT):
        self._send_cp3_pck(payload_bits, ip_addr, CP3Mode.PCK_1, ntp_mode)

    def send_pck_2(self,
                   payload_bits: str,
                   ip_addr: str,
                   ntp_mode: NTPMode = NTPMode.CLIENT):
        self._send_cp3_pck(payload_bits, ip_addr, CP3Mode.PCK_2, ntp_mode)

    def __restore_ntp_pck(self, ntp: NTP) -> NTP:
        self.log.debug('Send timestamp for reconstruction: ' + str(ntp.sent))
        sent_time_stamp = datetime.fromtimestamp(
            ntplib.ntp_to_system_time(ntp.sent))
        sent_time_stamp = sent_time_stamp.replace(year=datetime.now().year)
        sent_time_stamp_as_ntp = ntplib.system_to_ntp_time(
            sent_time_stamp.timestamp())
        ntp.sent = sent_time_stamp_as_ntp
        self.log.debug('Send timestamp after reconstruction: ' + str(ntp.sent))
        pck = CP3Package(ntp)

        if NTPMode.from_bit_string(pck.mode()) is NTPMode.CLIENT:
            self.log.debug("Restored in Client mode")
            ntp.ref = 0
            ntp.orig = 0
            ntp.recv = 0
        if NTPMode.from_bit_string(pck.mode()) is NTPMode.SERVER \
                or NTPMode.from_bit_string(pck.mode()) is NTPMode.BROADCAST_SERVER:
            self.log.debug("Restored in Server mode")
            origin_last_32 = pck.origin_timestamp()[32:64]
            received_last_32 = pck.receive_timestamp()[32:64]
            transmit_first_32 = pck.origin_timestamp()[0:32]

            pck.set_origin_timestamp(transmit_first_32 + origin_last_32)
            pck.set_receive_timestamp(transmit_first_32 + received_last_32)
            ntp = pck.ntp()
        self.log.debug("Reconstruction complete.")
        #ntp.show()
        return ntp

    def restore_pck(self, pck: CP3Package) -> NTP:
        """
        Restores the original values of the given CP3 packages, depending on the NTP mode.
        :param pck:
        :return:
        """
        ntp = pck.ntp()
        return self.__restore_ntp_pck(ntp)
Пример #6
0
class CP1Interceptor(CP1Client):
    def __init__(self,
                 address: str,
                 static_key: str,
                 sniff_interface: str = 'lo',
                 self_ip_addr='192.168.0.1',
                 payload: str = PAYLOAD_BITS_120,
                 client_address: str = ADDR_2,
                 log=logging.getLogger('CP1Interceptor-Logger')):
        super().__init__(address, static_key, sniff_interface, log)
        self.scapy_wrapper = ScapyWrapper()
        self.crypto_tools = NTPCrypto()
        self.self_ip_addr = self_ip_addr
        self.payload = payload
        self.client_address = client_address

    def listen(self):
        self.log.info(
            "Starting to listen for incoming NTP packages on interface: " +
            self.sniff_interface)
        while True:
            if self.self_ip_addr is None:
                pck = self.scapy_wrapper.next_ntp_packet(self.sniff_interface)
            else:
                pck = self.scapy_wrapper.next_ntp_packet_for_target(
                    self.sniff_interface, self.self_ip_addr)

            self.log.info("NTP-Package received")

            self.handle_incoming_pck(pck)

    def handle_incoming_pck(self, pck: Packet):
        ntp_pck = pck[NTP]
        cp1_pck = CP1Package(ntp_pck)
        self.log.info('Received pck bits: ' + str(cp1_pck._raw))

        if self.send_session is None:
            self.log.debug("Init new session (1).")
            self.send_session = CP1Session()
            next_pck = self.send_session.generate_init_pck(self.client_address)
            self.add_secret_payload(self.payload, self.static_key)

        else:
            next_bits_to_send = self.send_session.secret_to_send.next_bits(
                self.payload_size)
            self.log.debug("Next payload bits to send: " +
                           str(next_bits_to_send))
            new_cp1_pck = CP1Package(ntp_pck=init_ntp_client_pck())
            new_cp1_pck.add_payload(next_bits_to_send)
            next_pck = new_cp1_pck.ntp()

        upstream_pck = self.scapy_wrapper.get_upstream_ntp()

        upstream_pck[NTP].mode = 4
        # upstream_pck[NTP].orig = ntp_pck.sent
        upstream_pck[NTP].sent = next_pck[NTP].sent
        upstream_pck[NTP].ref = next_pck[NTP].ref
        upstream_pck[IP].src = pck[IP].dst
        upstream_pck[IP].dst = pck[IP].src
        upstream_pck[UDP].sport = pck[UDP].dport
        upstream_pck[UDP].dport = pck[UDP].sport

        up_raw = RawNTP(upstream_pck[NTP])
        pck_raw = RawNTP(ntp_pck)
        up_raw.set_origin_timestamp(pck_raw.transmit_timestamp())
        upstream_pck[NTP] = up_raw.ntp()

        self.log.debug("Created new CP1 packet to send...")

        upstream_pck.show()
        send(upstream_pck)

        if not self.has_next_pck():
            self.log.debug("Init new session (2).")
            self.send_session = None
Пример #7
0
class CP2Server:
    def __init__(self,
                 client_ip: str,
                 sniff_interface: str,
                 stratum_file_path: str = '/etc/stratum_override.txt',
                 num_pck_per_bit=1,
                 log: logging.Logger = logging.getLogger('CP2Server-logging')):
        """
        A CP2 Server that listens for incoming NTP requests and injects the manipulated stratum value to a config
        file on the harddrive
        :param log:
        """
        self.log = log
        self.stratum_file_path = stratum_file_path
        self.num_pck_per_bit = num_pck_per_bit
        self.client_ip = client_ip
        self.scapy_wrapper = ScapyWrapper()
        self.sniff_interface = sniff_interface
        self.payload_to_send = 'Hello World'
        self.payload_pointer = -1
        self.payload_bits = ''
        self.current_pck_num = 0
        self.current_stratum = None
        self.zero_mode = False
        self.init_done = False
        self.init_counter = 0

    def run(self):
        self.log.info("Starting CP2 Server and listening on interface " +
                      str(self.sniff_interface) +
                      " for client requests from " + str(self.client_ip))
        self.payload_bits = ascii_to_bit(self.payload_to_send)
        self.log.info('Sending payload "' + str(self.payload_to_send) +
                      '" decoded as ' + str(self.payload_bits))

        self.set_current_stratum()
        self.inject_next_bit()

        while True:
            next_pck = self.scapy_wrapper.next_ntp_packet(
                sniff_interface=self.sniff_interface)
            if next_pck[IP].src != self.client_ip:
                self.log.debug('Packet with ip ' + str(next_pck[IP]) +
                               ' was not meant for this server.')
                continue

            if not self.init_done:
                self.init_counter += 1
                if self.init_counter >= 3:
                    self.init_done = True
                    continue

            self.current_pck_num += 1

            if self.current_pck_num >= self.num_pck_per_bit:
                self.current_pck_num = 0
                self.set_current_stratum()
                self.inject_next_bit()
            else:
                self.log.debug('No change in stratum so far...')

    def set_current_stratum(self):
        """
        Sets the current stratum value based on the last bit value. It toggles the current stratum value in case
        the next bit is from the same type. This methods also sets and resets the current payload pointer accordingly.
        :return: the current Stratum value that was just set.
        """

        if self.payload_pointer % 8 == 0 and self.payload_pointer != 0 and not self.zero_mode:
            self.log.debug("One byte transfer complete " +
                           str(self.payload_bits[self.payload_pointer -
                                                 8:self.payload_pointer]))
            # self.zero_mode = True
            # self.current_stratum = CP2_NO_BITS[0]
            # return

        # if self.zero_mode:
        #    self.zero_mode = False

        if self.payload_pointer == len(self.payload_bits) - 1:
            self.log.info("Resetting the payload pointer")
            self.payload_pointer = -1
            self.payload_pointer += 1
            current_bit = self.payload_bits[self.payload_pointer]
            if current_bit == '0':
                self.current_stratum = CP2_ZERO_BITS[0]
            else:
                self.current_stratum = CP2_ONE_BITS[0]
            return self.current_stratum

        current_bit = self.payload_bits[self.payload_pointer]
        next_bit = self.payload_bits[self.payload_pointer + 1]
        self.payload_pointer += 1

        if current_bit == '0' and next_bit == '0':
            if self.current_stratum == CP2_ZERO_BITS[0]:
                self.current_stratum = CP2_ZERO_BITS[1]
            else:
                self.current_stratum = CP2_ZERO_BITS[0]
        elif current_bit == '1' and next_bit == '1':
            if self.current_stratum == CP2_ONE_BITS[0]:
                self.current_stratum = CP2_ONE_BITS[1]
            else:
                self.current_stratum = CP2_ZERO_BITS[0]
        elif next_bit == '1':
            self.current_stratum = CP2_ONE_BITS[0]
        else:
            self.current_stratum = CP2_ZERO_BITS[0]

        return self.current_stratum

    def inject_next_bit(self):
        """
        Writes the current stratum value to a file, where it can be read by a manipulated chrony NTP server.
        :return:
        """
        self.log.debug("Injecting next stratum " + str(self.current_stratum) +
                       " for bit: " +
                       str(self.payload_bits[self.payload_pointer]))
        with open(self.stratum_file_path, 'w') as file:
            file.write(str(self.current_stratum))
Пример #8
0
class CP1Server:
    """
    Provides functionality for a CP1 client (receive and send hidden messages).
    """
    def __init__(self,
                 cp1_address_bits: str,
                 static_decryption_key_bits: str,
                 sniff_interface: str = 'lo',
                 self_ip_addr: str = None,
                 log: logging.Logger = logging.getLogger('CP1Server-logger')):
        self.log = log
        self._address = cp1_address_bits
        self.static_key_bits = static_decryption_key_bits
        self.init_pck_field = NTPField.TRANSMIT_TIMESTAMP
        self.sniff_interface = sniff_interface
        self.listen_session: CP1ClientSession = None
        self._listen_lock = Lock()
        self._release_listen = False
        self.self_ip_addr = self_ip_addr
        self.scapy_wrapper = ScapyWrapper()
        self.crypto_tools = NTPCrypto()

    def address_and_version_check(self, pck: CP1Package) -> bool:
        """
        Checks whether a pck is meant for this client: Does the address and version number match?
        :param pck: The package to check
        :return: True in case it is meant for this client, False otherwise.
        """
        init_field = pck.get_field(self.init_pck_field)
        init_field = init_field[self.init_pck_field.length() -
                                8:self.init_pck_field.length()]
        received_address_bits_hashed = init_field[0:6]
        own_address_bits_hashed = generate_address_hash(
            pck.hash_nonce(), self._address)

        if received_address_bits_hashed != own_address_bits_hashed:
            self.log.debug('The received address hash did not match: ' +
                           received_address_bits_hashed)
            return False

        received_version_bits_hashed = init_field[6:8]
        own_version_bits_hashed = generate_version_hash(
            pck.hash_nonce(), _PROTOCOL_VERSION)

        if received_version_bits_hashed != own_version_bits_hashed:
            self.log.debug(
                'The received version hash did not match the expected value: '
                + str(received_version_bits_hashed))
            return False

        return True

    def listen(self):
        """
        Starts to listen for incoming NTP packages. This action is blocking so far.
        :return:
        """
        # TODO: Move this action to a new thread.
        if self._listen_lock.locked():
            return False
        self._listen_async()
        return True

    def _listen_async(self):
        """
        A synchronized method which waits for matching incoming NTP-packages and handles them accordingly to the
        current Session status (initialized, or non-initialized).
        :return:
        """
        self._listen_lock.acquire()
        try:
            self.log.info(
                "Lock acquired, starting to listen for incoming NTP packages on interface: "
                + self.sniff_interface)
            while True:

                if self.self_ip_addr is None:
                    pck = self.scapy_wrapper.next_ntp_packet(
                        self.sniff_interface)
                else:
                    pck = self.scapy_wrapper.next_ntp_packet_for_target(
                        self.sniff_interface, self.self_ip_addr)

                self.log.info("NTP-Package received")

                if self._release_listen:  # The listening should be aborted.
                    self._release_listen = False
                    return

                self.handle_incoming_ntp_pck(pck[NTP])

        finally:
            self.log.error("Session cleared after exception.")
            self.listen_session = None
            self._listen_lock.release()

    def handle_incoming_ntp_pck(self, ntp_pck: NTP):
        cp1_pck = CP1Package(ntp_pck)
        self.log.info('Received pck bits: ' + str(cp1_pck._raw))
        if self.listen_session is None:
            if not self.address_and_version_check(cp1_pck):
                self.log.info(
                    'Package did not contain matching address or version')
            else:
                self.log.info("Init pck. Creating new session")
                self.listen_session = CP1ClientSession(self.static_key_bits,
                                                       cp1_pck)
            return

        self.listen_session.add_next_pck(cp1_pck)

        if self.listen_session.is_complete():
            self.log.info("Payload completely received: " +
                          str(self.listen_session.secret_received_in_bits))
            self.log.info("Using key to decrypt: " +
                          str(self.listen_session.get_decryption_key_bytes()))
            decoded_bits = decrypt_bits_raw(
                encrypted_bits=self.listen_session.secret_received_in_bits,
                decryption_key_bytes=self.listen_session.
                get_decryption_key_bytes())
            self.log.info("DECRYPTED Payload: " + str(decoded_bits))
            self.listen_session = None