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!