Differences between version 7 and previous revision of CyberLeo/Scraps/cocknocker.
Other diffs: Previous Major Revision, Previous Author
Newer page: | version 7 | Last edited on Tuesday, 29 May 2012 9:04:29 | by CyberLeo | Revert |
Older page: | version 6 | Last edited on Tuesday, 29 May 2012 8:21:44 | by CyberLeo | Revert |
@@ -60,9 +60,14 @@
# 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'))
+ 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
#
@@ -131,48 +136,73 @@
#
# 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
+ 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
- list = nil
- until list && acceptable?(list)
- list = port_list
+ return @list if @list
+ @
list = nil
+ until @
list && acceptable?(@
list)
+ @
list = port_list
@generation += 1
raise Unacceptable if @generation > 255
end
- list
+ @
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!
+#
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>
version 7
# 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