Differences between version 4 and previous revision of CyberLeo/Scraps/cocknocker.
Other diffs: Previous Major Revision, Previous Author
| Newer page: | version 4 | Last edited on Tuesday, 29 May 2012 8:01:27 | by CyberLeo | Revert |
| Older page: | version 3 | Last edited on Tuesday, 29 May 2012 8:00:23 | by CyberLeo | Revert |
@@ -77,9 +77,9 @@
#
# Returns a string containing the packed information.
def pack
a = [ generation ]
- a.concat(ip.split('.').map(&:to_i)
+ a.concat(ip.split('.').map(&:to_i)
)
a << time
p = a.pack('C5N')
a << Zlib.crc32(p)
a.pack('C5NN')
version 4
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!
