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!
