FindPage
View Source:
CyberLeo/Scraps/cocknocker
<code brush="ruby"> # Copyright (c) 2012 CyberLeo, All Rights Reserved. # http://wiki.cyberleo.net/wiki/CyberLeo/COPYRIGHT?version=4 require 'openssl' require 'socket' require 'zlib' # Public: CocKnocker, the Cryptographically Obfuscated Contact Knocker # # 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) data = packet.unpack('C5NN') generation = data[0] ip = data[1..4].map(&:to_s).join('.') time = data[5] crc = data[6] self.new(generation, ip, time) 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 @generation = 0 @time = Time.now end # Internal: Craft and return a packet object corresponding to the attributes def packet Packet.new(@generation, my_ip, @time.to_i) 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.to_s) 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 = [] stack = encrypted_packet.unpack('C*') while a = stack.shift b = stack.shift c = stack.shift out << ( ( a & 0xff ) << 4 ) + ( ( b & 0xf0 ) >> 4 ) out << ( ( b & 0x0f ) << 8 ) + ( c & 0xff ) end out.each_with_index {|p, i| out[i] = ( ( p << 4 ) + i ) } 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 return @list if @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, tgt_ip, port) } end class Listener def self.decode(key, list) # Sort the stack, to ensure the packets are in the correct order, and # strip off packet indexes stack = list.sort {|a,b| ( a & 0x000f ) <=> ( b & 0x000f ) }.map {|port| ( port & 0xfff0 ) >> 4 } # Decode 12b16b encoding out = [] while one = stack.shift two = stack.shift out << ( ( one & 0xff0 ) >> 4 ) out << ( ( one & 0x00f ) << 4 ) + ( ( two & 0xf00 ) >> 8 ) out << ( two & 0x0ff ) end # Decrypt packet data = out.pack('C*') data = key.public_decrypt(data) Packet.unpack(data) end 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! c = CocKnocker.new(key, tgt_ip, my_ip) puts c.packet.inspect p = c.safe_port_list.sort puts CocKnocker::Listener.decode(key, p).inspect </code>