Note: You are viewing an old version of this page. View the current version.

Differences between version 3 and previous revision of CyberLeo/Scraps/cocknocker.

Other diffs: Previous Major Revision, Previous Author

Newer page: version 3 Last edited on Tuesday, 29 May 2012 8:00:23 by CyberLeo Revert
Older page: version 2 Last edited on Tuesday, 29 May 2012 7:10:38 by CyberLeo Revert
@@ -2,61 +2,168 @@
 require 'openssl' 
 require 'socket' 
 require 'zlib' 
  
-BAD_PORTS = [ 0 , 25 , 135 , 139 , 445 ]  
+# Public: Main knock class  
+#  
+# This class is responsible for computing a cryptographic sequence of ports to  
+# knock , via UDP packets , to tickle a firewall to allow the source IP through.  
+class CocKnocker  
+ # Public: Raised when 'generation' wraps 8 bits and none of the packets have  
+ # been deemed acceptable. This should , in practice , never occur unless you  
+ # have done something seriously wrong.  
+ class Unacceptable < StandardError; end  
  
-def packet(generation, ip, time)  
- a = [ generation ]  
- a.concat( ip.split('.').map(& :to _i))  
- a << time  
- p = a.pack('C5N')  
- a << Zlib.crc32(p)  
- a.pack('C5NN')  
-end  
+ # Public: See initializer documentation for descriptions of these attributes.  
+ attr_accessor :key, :tgt_ ip, :my _ip  
  
-def encrypt(packet , key)  
- key .private_encrypt(packet)  
-end  
+ # Public: A list of ports which should be avoided when knocking , due to their  
+ # special meaning or likelihood of being blocked by either source or target  
+ # internet service providers .  
+ BAD_PORTS = [ 0, 25, 135, 139, 445 ].freeze  
  
-def fmt(packet)  
- out = []  
- idx = 0  
- stack = packet.unpack ('C* ')  
- while a = stack .shift  
- b = stack .shift  
- c = stack .shift  
- one = ( a << 4 ) + ( b & 0xf )  
- two = ( ( b & 0x0f ) << 8 ) + c  
- out << ( ( one << 4 ) + idx )  
- idx + = 1  
- out << ( ( two << 4 ) + idx )  
- idx += 1  
+ # Public: Packet encapsulation and handling class.  
+ #  
+ # Packet format is as follows:  
+ # uint8_t generation - Integer to alter packet; increment this to generate  
+ # a different packet in case the generated port list  
+ # contains items that are found in the BAD_PORTS list .  
+ # uint32_t ip - IP address of the node attempting to knock (the  
+ # 'client '), to mitigate replay attacks.  
+ # uint32_t time - UNIX Epoch timestamp of when the knock is occuring,  
+ # to mitigate replay attacks.  
+ # uint32_t crc - CRC32 checksum of the first 9 bytes of the encoded  
+ # representation of the packet, to ensure a successful  
+ # decode .  
+ # All values are encoded in network byte order.  
+ class Packet  
+ # Public: See class documentation for descriptions of these attributes .  
+ attr_accessor :generation, :ip, :time  
+  
+ # Public: Compute the CRC32 checksum of the passed binary packet string, to  
+ # ensure that it matches what is encoded in the packet itself .  
+ #  
+ # string packet - Encoded packet to verify  
+ #  
+ # Returns true if the computed CRC32 checksum of the first 9 bytes of the  
+ # provided packet matches the CRC32 checksum embedded in the last four  
+ # bytes; false otherwise.  
+ def self.valid? (packet )  
+ packet.unpack ('C5NN' ).last == Zlib.crc32 (packet[0.. 8] )  
+ end  
+  
+ # Public: Unpack a packet for use.  
+ #  
+ # string packet - Encoded packet to decode  
+ # Returns a Packet instance containing the decoded values; or false if the  
+ # checksum does not match.  
+ def self.unpack (packet)  
+ return false unless self.valid? (packet)  
+ self.new(*packet.unpack('C5NN' ))  
+ end  
+  
+ # Public: Instance initializer  
+ #  
+ # integer generation - See class documentation  
+ # string ip - See class documentation  
+ # integer time - See class documentation  
+ def initialize(generation, ip, time)  
+ @generation = generation  
+ @ip = ip  
+ @time = time  
+ end  
+  
+ # Internal: Transform the generation, IP, time, and checksum into a packed  
+ # binary string, according to the format set forth in the class docs.  
+ #  
+ # Returns a string containing the packed information.  
+ def pack  
+ a = [ generation ]  
+ a.concat(ip.split('.').map(&:to_i)  
+ a << time  
+ p = a.pack ('C5N')  
+ a << Zlib.crc32(p )  
+ a.pack('C5NN' )  
+ end  
+ alias :to_s :pack  
  end 
- out  
-end  
  
-# Returns true if the packet contains no bad ports; false if it does  
-def check(packet, bad_ports)  
- bad_ports - packet == bad_ports  
-end  
  
-def knock(ip, key)  
- time = Time.now. to_i  
- generation = 0  
- packet = nil  
- until packet && check (packet , BAD _PORTS )  
- packet = fmt(encrypt(packet(generation, ip, time), key))  
- generation + = 1  
+ # Public: Instance initializer  
+ #  
+ # key - OpenSSL::PKey::RSA object representing a 192-bit RSA private key;  
+ # used to obfuscate packets prior to their transmission, and to offer  
+ # a means of authenticating the knocker during decryption.  
+ # tgt _ip - String IP address of the firewall that should receive the knocks.  
+ # my_ip - String public IP address of the device knocking; should match the  
+ # IP seen by the firewall, to ensure security against replay attacks.  
+ def initialize (key , tgt _ip, my_ip )  
+ @key = key  
+ @tgt_ip = tgt_ip  
+ @my_ip = my_ip  
  end 
  
- sock = UDPSocket.new  
- packet .each {|port|  
- sock.send('', 0, ip, port)  
-
+ # Internal: Encrypt the packet to the provided key  
+ #  
+ # Returns a string containing the packet encrypted against the key.  
+ def encrypted_packet  
+ key.private_encrypt(@packet)  
+ end  
+  
+ # Internal: Checks if the provided port list is acceptable.  
+ #  
+ # Returns true if the provided list does not contain any ports on the  
+ # BAD_PORTS list, false otherwise.  
+ def acceptable?(list)  
+ BAD_PORTS - list == BAD_PORTS  
+ end  
+  
+ # Internal: Format a packet into a sequenced series of port numbers suitable for  
+ # knocking.  
+ #  
+ # Returns an array of integers between 0 and 65535 inclusive.  
+ def port_list  
+ out = []  
+ idx = 0  
+ stack = encrypted_packet.unpack('C*')  
+ while a = stack.shift  
+ b = stack.shift  
+ c = stack.shift  
+ one = ( a << 4 ) + ( b & 0xf )  
+ two = ( ( b & 0x0f ) << 8 ) + c  
+ out << ( ( one << 4 ) + idx )  
+ idx += 1  
+ out << ( ( two << 4 ) + idx )  
+ idx += 1  
+ end  
+ out  
+ end  
+  
+ # Internal: Same as #port_list, but makes sure the port list is #acceptable?  
+ #  
+ # Returns an array of integers between 0 and 65535 inclusive, excluding ports  
+ # in the BAD_PORTS list.  
+ def safe_port_list  
+ list = nil  
+ until list && acceptable?(list)  
+ list = port_list  
+ generation += 1  
+ raise Unacceptable if generation > 255  
+ end  
+ list  
+ end  
+  
+ # Public: Perform a knock sequence  
+ def knock!  
+ sock = UDPSocket.new  
+ safe_port_list .each {|port|  
+ sock.send('', 0, dst_ ip, port)  
+ }  
+ end  
 end 
  
 key = OpenSSL::PKey::RSA.new(192) 
-ip = '127 .0.0.1' 
+my_ ip = '10.0.0.2'  
+tgt_ip = '10 .0.0.1' 
  
-knock (ip) 
+CocKnocker.new (key, tgt_ip, my_ ip).knock!  
 </code> 

version 3

require 'openssl'
require 'socket'
require 'zlib'

# Public: Main knock class
#
# This class is responsible for computing a cryptographic sequence of ports to
# knock, via UDP packets, to tickle a firewall to allow the source IP through.
class CocKnocker
  # Public: Raised when 'generation' wraps 8 bits and none of the packets have
  # been deemed acceptable. This should, in practice, never occur unless you
  # have done something seriously wrong.
  class Unacceptable < StandardError; end

  # Public: See initializer documentation for descriptions of these attributes.
  attr_accessor :key, :tgt_ip, :my_ip

  # Public: A list of ports which should be avoided when knocking, due to their
  # special meaning or likelihood of being blocked by either source or target
  # internet service providers.
  BAD_PORTS = [ 0, 25, 135, 139, 445 ].freeze

  # Public: Packet encapsulation and handling class.
  #
  # Packet format is as follows:
  #   uint8_t  generation - Integer to alter packet; increment this to generate
  #                         a different packet in case the generated port list
  #                         contains items that are found in the BAD_PORTS list.
  #   uint32_t ip         - IP address of the node attempting to knock (the
  #                         'client'), to mitigate replay attacks.
  #   uint32_t time       - UNIX Epoch timestamp of when the knock is occuring,
  #                         to mitigate replay attacks.
  #   uint32_t crc        - CRC32 checksum of the first 9 bytes of the encoded
  #                         representation of the packet, to ensure a successful
  #                         decode.
  # All values are encoded in network byte order.
  class Packet
    # Public: See class documentation for descriptions of these attributes.
    attr_accessor :generation, :ip, :time

    # Public: Compute the CRC32 checksum of the passed binary packet string, to
    # ensure that it matches what is encoded in the packet itself.
    #
    # string packet - Encoded packet to verify
    #
    # Returns true if the computed CRC32 checksum of the first 9 bytes of the
    # provided packet matches the CRC32 checksum embedded in the last four
    # bytes; false otherwise.
    def self.valid?(packet)
      packet.unpack('C5NN').last == Zlib.crc32(packet[0..8])
    end

    # Public: Unpack a packet for use.
    #
    # string packet - Encoded packet to decode
    # Returns a Packet instance containing the decoded values; or false if the
    # checksum does not match.
    def self.unpack(packet)
      return false unless self.valid?(packet)
      self.new(*packet.unpack('C5NN'))
    end

    # Public: Instance initializer
    #
    # integer generation - See class documentation
    # string ip          - See class documentation
    # integer time       - See class documentation
    def initialize(generation, ip, time)
      @generation = generation
      @ip = ip
      @time = time
    end

    # Internal: Transform the generation, IP, time, and checksum into a packed
    # binary string, according to the format set forth in the class docs.
    #
    # Returns a string containing the packed information.
    def pack
      a = [ generation ]
      a.concat(ip.split('.').map(&:to_i)
      a << time
      p = a.pack('C5N')
      a << Zlib.crc32(p)
      a.pack('C5NN')
    end
    alias :to_s :pack
  end


  # Public: Instance initializer
  #
  # key    - OpenSSL::PKey::RSA object representing a 192-bit RSA private key;
  #          used to obfuscate packets prior to their transmission, and to offer
  #          a means of authenticating the knocker during decryption.
  # tgt_ip - String IP address of the firewall that should receive the knocks.
  # my_ip  - String public IP address of the device knocking; should match the
  #          IP seen by the firewall, to ensure security against replay attacks.
  def initialize(key, tgt_ip, my_ip)
    @key = key
    @tgt_ip = tgt_ip
    @my_ip = my_ip
  end

  # Internal: Encrypt the packet to the provided key
  #
  # Returns a string containing the packet encrypted against the key.
  def encrypted_packet
    key.private_encrypt(@packet)
  end

  # Internal: Checks if the provided port list is acceptable.
  #
  # Returns true if the provided list does not contain any ports on the
  # BAD_PORTS list, false otherwise.
  def acceptable?(list)
    BAD_PORTS - list == BAD_PORTS
  end

  # Internal: Format a packet into a sequenced series of port numbers suitable for
  # knocking.
  #
  # Returns an array of integers between 0 and 65535 inclusive.
  def port_list
    out = []
    idx = 0
    stack = encrypted_packet.unpack('C*')
    while a = stack.shift
      b = stack.shift
      c = stack.shift
      one = ( a << 4 ) + ( b & 0xf )
      two = ( ( b & 0x0f ) << 8 ) + c
      out << ( ( one << 4 ) + idx )
      idx += 1
      out << ( ( two << 4 ) + idx )
      idx += 1
    end
    out
  end

  # Internal: Same as #port_list, but makes sure the port list is #acceptable?
  #
  # Returns an array of integers between 0 and 65535 inclusive, excluding ports
  # in the BAD_PORTS list.
  def safe_port_list
    list = nil
    until list && acceptable?(list)
      list = port_list
      generation += 1
      raise Unacceptable if generation > 255
    end
    list
  end

  # Public: Perform a knock sequence
  def knock!
    sock = UDPSocket.new
    safe_port_list.each {|port|
      sock.send('', 0, dst_ip, port)
    }
  end
end

key = OpenSSL::PKey::RSA.new(192)
my_ip = '10.0.0.2'
tgt_ip = '10.0.0.1'

CocKnocker.new(key, tgt_ip, my_ip).knock!