Move Kademlia stuff to a more OO model, etc, per reviews
All of the work is done in rex. The msf mixin just prevents the desire to call rex directly from the modulebug/bundler_fix
parent
e255db9429
commit
0ed356f71c
|
@ -1,4 +1,5 @@
|
||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
require 'rex/proto/kademlia'
|
require 'rex/proto/kademlia'
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
|
@ -10,109 +11,5 @@ module Msf
|
||||||
###
|
###
|
||||||
module Auxiliary::Kademlia
|
module Auxiliary::Kademlia
|
||||||
include Rex::Proto::Kademlia
|
include Rex::Proto::Kademlia
|
||||||
|
|
||||||
# Opcode for a BOOTSTRAP request
|
|
||||||
BOOTSTRAP_REQ = 0x01
|
|
||||||
# Opcode for a BOOTSTRAP response
|
|
||||||
BOOTSTRAP_RES = 0x09
|
|
||||||
# Opcode for a PING request
|
|
||||||
PING = 0x60
|
|
||||||
# Opcode for a PING response
|
|
||||||
PONG = 0x61
|
|
||||||
# The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
|
|
||||||
# peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
|
|
||||||
# and version (1 byte)
|
|
||||||
BOOTSTRAP_PEER_SIZE = 25
|
|
||||||
|
|
||||||
# Builds a BOOTSTRAP request
|
|
||||||
#
|
|
||||||
# @return [String] a BOOTSTRAP request
|
|
||||||
def bootstrap
|
|
||||||
Message.new(BOOTSTRAP_REQ)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Decodes a BOOTSTRAP response
|
|
||||||
#
|
|
||||||
# @param response [String] the response to decode
|
|
||||||
# @return [Array] the discovered peer ID, TCP port, version and a list of peers
|
|
||||||
# if the response if valid, nil otherwise
|
|
||||||
def decode_bootstrap_res(response)
|
|
||||||
message = Message.from_data(response)
|
|
||||||
# abort if this isn't a valid response
|
|
||||||
return nil unless message.type = BOOTSTRAP_RES
|
|
||||||
return nil unless message.body.size >= 23
|
|
||||||
peer_id = decode_peer_id(message.body.slice!(0,16))
|
|
||||||
tcp_port, version, num_peers = message.body.slice!(0,5).unpack('vCv')
|
|
||||||
# protocol says there are no peers and the body confirms this, so just return with no peers
|
|
||||||
return [ tcp_port, version, []] if num_peers == 0 && message.body.blank?
|
|
||||||
peers = decode_bootstrap_peers(message.body)
|
|
||||||
# abort if the peer data was invalid
|
|
||||||
return nil unless peers
|
|
||||||
[ peer_id, tcp_port, version, peers ]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Builds a PING request
|
|
||||||
#
|
|
||||||
# @return [String] a PING request
|
|
||||||
def ping
|
|
||||||
Message.new(PING)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Decode a PING response, PONG
|
|
||||||
#
|
|
||||||
# @param response [String] the response to decode
|
|
||||||
# @return [Integer] the source port from the PING response if the response is valid, nil otherwise
|
|
||||||
def decode_pong(response)
|
|
||||||
message = Message.from_data(response)
|
|
||||||
# abort if this isn't a pong
|
|
||||||
return nil unless message.type == PONG
|
|
||||||
# abort if the response is too large/small
|
|
||||||
return nil unless message.body && message.body.size == 2
|
|
||||||
# this should always be equivalent to the source port from which the PING was received
|
|
||||||
message.body.unpack('v')[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Decode a list of peers from a BOOTSTRAP response
|
|
||||||
#
|
|
||||||
# @param peers_data [String] the peers data from a BOOTSTRAP response
|
|
||||||
# @return [Array] a list of the peers and their associated metadata extracted
|
|
||||||
# from the response if valid, nil otherwise
|
|
||||||
def decode_bootstrap_peers(peers_data)
|
|
||||||
# sanity check total size
|
|
||||||
return nil unless peers_data.size % BOOTSTRAP_PEER_SIZE == 0
|
|
||||||
peers = []
|
|
||||||
until peers_data.blank?
|
|
||||||
peers << decode_bootstrap_peer(peers_data.slice!(0, BOOTSTRAP_PEER_SIZE))
|
|
||||||
end
|
|
||||||
peers
|
|
||||||
end
|
|
||||||
|
|
||||||
# Decodes a single set of peer data from a BOOTSTRAP reseponse
|
|
||||||
#
|
|
||||||
# @param peer-data [String] the peer data for one peer from a BOOSTRAP response
|
|
||||||
# @return [Array] the peer ID, IPv4 addresss, UDP port, TCP port and version of this peer
|
|
||||||
def decode_bootstrap_peer(peer_data)
|
|
||||||
# sanity check the size of this peer's data
|
|
||||||
return nil unless peer_data.size == BOOTSTRAP_PEER_SIZE
|
|
||||||
# TODO; interpret this properly
|
|
||||||
peer_id = peer_data.slice!(0, 16)
|
|
||||||
ip, udp_port, tcp_port, version = peer_data.unpack('VvvC')
|
|
||||||
[ decode_peer_id(peer_id), Rex::Socket.addr_itoa(ip), udp_port, tcp_port, version ]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Decodes an on-the-wire representation of a Kademlia peer to its 16-character hex equivalent
|
|
||||||
#
|
|
||||||
# @param bytes [String] the on-the-wire representation of a Kademlia peer
|
|
||||||
# @return [String] the peer ID if valid, nil otherwise
|
|
||||||
def decode_peer_id(bytes)
|
|
||||||
peer_id = 0
|
|
||||||
return nil unless bytes.size == 16
|
|
||||||
bytes.unpack('VVVV').map { |p| peer_id <<= 32; peer_id ^= p; }
|
|
||||||
peer_id.to_s(16).upcase
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# def encode_peer_id(id)
|
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex/proto/kademlia/bootstrap_request'
|
||||||
|
require 'rex/proto/kademlia/bootstrap_response'
|
||||||
require 'rex/proto/kademlia/message'
|
require 'rex/proto/kademlia/message'
|
||||||
|
require 'rex/proto/kademlia/ping'
|
||||||
|
require 'rex/proto/kademlia/pong'
|
||||||
|
require 'rex/proto/kademlia/util'
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex/proto/kademlia/message'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Proto
|
||||||
|
module Kademlia
|
||||||
|
# Opcode for a BOOTSTRAP request
|
||||||
|
BOOTSTRAP_REQUEST = 0x01
|
||||||
|
|
||||||
|
# A Kademlia bootstrap request message
|
||||||
|
class BootstrapRequest < Message
|
||||||
|
def initialize
|
||||||
|
super(BOOTSTRAP_REQUEST)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,70 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex/proto/kademlia/message'
|
||||||
|
require 'rex/proto/kademlia/util'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Proto
|
||||||
|
module Kademlia
|
||||||
|
# Opcode for a bootstrap response
|
||||||
|
BOOTSTRAP_RESPONSE = 0x09
|
||||||
|
|
||||||
|
# A Kademlia bootstrap response message
|
||||||
|
class BootstrapResponse < Message
|
||||||
|
attr_reader :peer_id
|
||||||
|
attr_reader :tcp_port
|
||||||
|
attr_reader :version
|
||||||
|
# An array of hashes containing the peer ID, IP address, UDP and TCP ports as well as the type/version
|
||||||
|
attr_reader :peers
|
||||||
|
|
||||||
|
def initialize(peer_id, tcp_port, version, peers)
|
||||||
|
@peer_id = peer_id
|
||||||
|
@tcp_port = tcp_port
|
||||||
|
@version = version
|
||||||
|
@peers = peers
|
||||||
|
end
|
||||||
|
|
||||||
|
# The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
|
||||||
|
# peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
|
||||||
|
# and version (1 byte)
|
||||||
|
BOOTSTRAP_PEER_SIZE = 25
|
||||||
|
|
||||||
|
# Builds a bootstrap response from given data
|
||||||
|
#
|
||||||
|
# @param data [String] the data to decode
|
||||||
|
# @return [BootstrapResponse] the bootstrap response if the data is valid, nil otherwise
|
||||||
|
def self.from_data(data)
|
||||||
|
message = Message.from_data(data)
|
||||||
|
# abort if this isn't a valid response
|
||||||
|
return unless message
|
||||||
|
return unless message.type == BOOTSTRAP_RESPONSE
|
||||||
|
return unless message.body.size >= 23
|
||||||
|
bootstrap_peer_id = Rex::Proto::Kademlia.decode_peer_id(message.body.slice!(0, 16))
|
||||||
|
bootstrap_tcp_port, bootstrap_version, num_peers = message.body.slice!(0, 5).unpack('vCv')
|
||||||
|
# protocol says there are no peers and the body confirms this, so just return with no peers
|
||||||
|
if num_peers == 0 && message.body.blank?
|
||||||
|
peers = []
|
||||||
|
else
|
||||||
|
peers_data = message.body
|
||||||
|
# peers data is too long/short, abort
|
||||||
|
return if peers_data.size % BOOTSTRAP_PEER_SIZE != 0
|
||||||
|
peers = []
|
||||||
|
until peers_data.blank?
|
||||||
|
peer_data = peers_data.slice!(0, BOOTSTRAP_PEER_SIZE)
|
||||||
|
peer_id = Rex::Proto::Kademlia.decode_peer_id(peer_data.slice!(0, 16))
|
||||||
|
ip, udp_port, tcp_port, version = peer_data.unpack('VvvC')
|
||||||
|
peers << {
|
||||||
|
id: peer_id,
|
||||||
|
ip: Rex::Socket.addr_itoa(ip),
|
||||||
|
tcp_port: tcp_port,
|
||||||
|
udp_port: udp_port,
|
||||||
|
version: version
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
BootstrapResponse.new(bootstrap_peer_id, bootstrap_tcp_port, bootstrap_version, peers)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,12 +17,12 @@ module Proto
|
||||||
#
|
#
|
||||||
##
|
##
|
||||||
module Kademlia
|
module Kademlia
|
||||||
# The header that non-compressed Kad messages use
|
|
||||||
STANDARD_PACKET = 0xE4
|
|
||||||
# The header that compressed Kad messages use, which is currently unsupported
|
|
||||||
COMPRESSED_PACKET = 0xE5
|
|
||||||
|
|
||||||
class Message
|
class Message
|
||||||
|
# The header that non-compressed Kad messages use
|
||||||
|
STANDARD_PACKET = 0xE4
|
||||||
|
# The header that compressed Kad messages use, which is currently unsupported
|
||||||
|
COMPRESSED_PACKET = 0xE5
|
||||||
|
|
||||||
attr_accessor :type, :body
|
attr_accessor :type, :body
|
||||||
|
|
||||||
# @param type [String] the message type
|
# @param type [String] the message type
|
||||||
|
@ -36,7 +36,7 @@ module Kademlia
|
||||||
return if data.length < 2
|
return if data.length < 2
|
||||||
header, type = data.unpack('CC')
|
header, type = data.unpack('CC')
|
||||||
if header == COMPRESSED_PACKET
|
if header == COMPRESSED_PACKET
|
||||||
fail NotImplementedError, "Unable to handle #{message.length}-byte compressed Kademlia message"
|
fail NotImplementedError, "Unable to handle #{data.length}-byte compressed Kademlia message"
|
||||||
end
|
end
|
||||||
return if header != STANDARD_PACKET
|
return if header != STANDARD_PACKET
|
||||||
Message.new(type, data[2, data.length])
|
Message.new(type, data[2, data.length])
|
||||||
|
@ -45,6 +45,10 @@ module Kademlia
|
||||||
def to_str
|
def to_str
|
||||||
[STANDARD_PACKET, @type].pack('CC') + @body
|
[STANDARD_PACKET, @type].pack('CC') + @body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
type == other.type && body == other.body
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex/proto/kademlia/message'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Proto
|
||||||
|
module Kademlia
|
||||||
|
# Opcode for a PING request
|
||||||
|
PING = 0x60
|
||||||
|
|
||||||
|
# A Kademlia ping message.
|
||||||
|
class Ping < Message
|
||||||
|
def initialize
|
||||||
|
super(PING)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'rex/proto/kademlia/message'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Proto
|
||||||
|
module Kademlia
|
||||||
|
# Opcode for a PING response
|
||||||
|
PONG = 0x61
|
||||||
|
|
||||||
|
# A Kademlia pong message.
|
||||||
|
class Pong < Message
|
||||||
|
# the source port from which the PING was received
|
||||||
|
attr_reader :port
|
||||||
|
|
||||||
|
def initialize(port = nil)
|
||||||
|
super(PONG)
|
||||||
|
@port = port
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_data(data)
|
||||||
|
message = super(data)
|
||||||
|
return if message.type != PONG
|
||||||
|
return if message.body.size != 2
|
||||||
|
Pong.new(message.body.unpack('v')[0])
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_str
|
||||||
|
super + [@port].pack('v')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Proto
|
||||||
|
module Kademlia
|
||||||
|
# Decodes an on-the-wire representation of a Kademlia peer to its 16-character hex equivalent
|
||||||
|
#
|
||||||
|
# @param bytes [String] the on-the-wire representation of a Kademlia peer
|
||||||
|
# @return [String] the peer ID if valid, nil otherwise
|
||||||
|
def self.decode_peer_id(bytes)
|
||||||
|
peer_id = 0
|
||||||
|
return nil unless bytes.size == 16
|
||||||
|
bytes.unpack('VVVV').map { |p| peer_id = ((peer_id << 32) ^ p) }
|
||||||
|
peer_id.to_s(16).upcase
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# def encode_peer_id(id)
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -46,9 +46,9 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
def build_probe
|
def build_probe
|
||||||
@probe ||= case action.name
|
@probe ||= case action.name
|
||||||
when 'BOOTSTRAP'
|
when 'BOOTSTRAP'
|
||||||
bootstrap
|
BootstrapRequest.new
|
||||||
when 'PING'
|
when 'PING'
|
||||||
ping
|
Ping.new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -58,22 +58,22 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
case action.name
|
case action.name
|
||||||
when 'BOOTSTRAP'
|
when 'BOOTSTRAP'
|
||||||
peer_id, tcp_port, version, peers = decode_bootstrap_res(response)
|
if bootstrap_res = BootstrapResponse.from_data(response)
|
||||||
info = {
|
info = {
|
||||||
peer_id: peer_id,
|
peer_id: bootstrap_res.peer_id,
|
||||||
tcp_port: tcp_port,
|
tcp_port: bootstrap_res.tcp_port,
|
||||||
version: version,
|
version: bootstrap_res.version,
|
||||||
peers: peers
|
peers: bootstrap_res.peers
|
||||||
}
|
}
|
||||||
if datastore['VERBOSE']
|
print_good("#{peer} ID #{bootstrap_res.peer_id}, TCP port #{bootstrap_res.tcp_port}," +
|
||||||
else
|
" version #{bootstrap_res.version}, #{bootstrap_res.peers.size} peers")
|
||||||
print_good("#{peer} ID #{peer_id}, TCP port #{tcp_port}, version #{version}, #{peers.size} peers")
|
|
||||||
end
|
end
|
||||||
when 'PING'
|
when 'PING'
|
||||||
udp_port = decode_pong(response)
|
if pong = Pong.from_data(response)
|
||||||
print_good("#{peer} PONG")
|
print_good("#{peer} PONG port #{pong.port}")
|
||||||
# udp_port should match the port we contacted it from. TODO: validate this?
|
# port should match the port we contacted it from. TODO: validate this?
|
||||||
info = { udp_port: udp_port }
|
info = { udp_port: pong.port }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return unless info
|
return unless info
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
|
#
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'msf/core/auxiliary/kademlia'
|
require 'msf/core/auxiliary/kademlia'
|
||||||
|
|
||||||
|
@ -8,109 +9,4 @@ describe Msf::Auxiliary::Kademlia do
|
||||||
mod.extend described_class
|
mod.extend described_class
|
||||||
mod
|
mod
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#decode_pong' do
|
|
||||||
it 'does not decode overly small pongs' do
|
|
||||||
expect(kad.decode_pong("\xE4\x61\x01")).to eq(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not decode overly large pongs' do
|
|
||||||
expect(kad.decode_pong("\xE4\x61\x01\x02\x03")).to eq(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'properly decodes valid pongs' do
|
|
||||||
expect(kad.decode_pong("\xE4\x61\x9E\x86")).to eq(34462)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#decode_bootstrap_peer' do
|
|
||||||
it 'does not decode overly small peer responses' do
|
|
||||||
expect(kad.decode_bootstrap_peer("this is too small")).to eq(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not decode overly large peer responses' do
|
|
||||||
expect(kad.decode_bootstrap_peer("this is much, much, much too large")).to eq(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'properly extracts peer info' do
|
|
||||||
data =
|
|
||||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + # peer ID
|
|
||||||
"\x04\x28\xA8\xC0" + # 192.168.40.4
|
|
||||||
"\x31\xd4" + # UDP port 54321
|
|
||||||
"\x39\x30" + # TCP port 12345
|
|
||||||
"\x08" # peer type
|
|
||||||
peer_id, ip, udp_port, tcp_port, type = kad.decode_bootstrap_peer(data)
|
|
||||||
expect(peer_id).to eq('3020100070605040B0A09080F0E0D0C')
|
|
||||||
expect(ip).to eq('192.168.40.4')
|
|
||||||
expect(udp_port).to eq(54321)
|
|
||||||
expect(tcp_port).to eq(12345)
|
|
||||||
expect(type).to eq(8)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#decode_bootstrap_peers' do
|
|
||||||
it 'does not decode overly small bootstrap responses' do
|
|
||||||
expect(kad.decode_bootstrap_peer("this is too small")).to eq(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not decode overly large bootstrap responses' do
|
|
||||||
expect(kad.decode_bootstrap_peer("this is large enough but truncated")).to eq(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'properly extracts peers info' do
|
|
||||||
data =
|
|
||||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + # peer ID
|
|
||||||
"\x04\x28\xA8\xC0" + # 192.168.40.4
|
|
||||||
"\x31\xd4" + # UDP port 54321
|
|
||||||
"\x39\x30" + # TCP port 12345
|
|
||||||
"\x08" + # peer type
|
|
||||||
"\x01\x01\x02\x02\x03\x03\x04\x04\x05\x05\x06\x06\x07\x07\x08\x08" + # peer ID
|
|
||||||
"\x05\x28\xA8\xC0" + # 192.168.40.5
|
|
||||||
"\x5c\x11" + # UDP port 4444
|
|
||||||
"\xb3\x15" + # TCP port 5555
|
|
||||||
"\x09" # peer type
|
|
||||||
peers = kad.decode_bootstrap_peers(data)
|
|
||||||
expect(peers.size).to eq(2)
|
|
||||||
peer1_id, peer1_ip, peer1_udp, peer1_tcp, peer1_type = peers.first
|
|
||||||
expect(peer1_id).to eq('3020100070605040B0A09080F0E0D0C')
|
|
||||||
expect(peer1_ip).to eq('192.168.40.4')
|
|
||||||
expect(peer1_udp).to eq(54321)
|
|
||||||
expect(peer1_tcp).to eq(12345)
|
|
||||||
expect(peer1_type).to eq(8)
|
|
||||||
peer2_id, peer2_ip, peer2_udp, peer2_tcp, peer2_type = peers.last
|
|
||||||
expect(peer2_id).to eq('2020101040403030606050508080707')
|
|
||||||
expect(peer2_ip).to eq('192.168.40.5')
|
|
||||||
expect(peer2_udp).to eq(4444)
|
|
||||||
expect(peer2_tcp).to eq(5555)
|
|
||||||
expect(peer2_type).to eq(9)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#decode_bootstrap_res' do
|
|
||||||
it 'properly decodes valid bootstrap responses' do
|
|
||||||
data = IO.read(File.join(File.dirname(__FILE__), 'kademlia_bootstrap_res.bin'))
|
|
||||||
peer_id, tcp, version, peers = kad.decode_bootstrap_res(data)
|
|
||||||
expect(peer_id).to eq('B54A83462529B21EF51FD54B956B07B0')
|
|
||||||
expect(tcp).to eq(4662)
|
|
||||||
expect(version).to eq(8)
|
|
||||||
# don't bother checking every peer
|
|
||||||
expect(peers.size).to eq(20)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#decode_peer_id' do
|
|
||||||
it 'decodes a peer ID properly' do
|
|
||||||
bytes = "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71"
|
|
||||||
peer_id = "9B896000AEBE0B0A0ECB35457177A107"
|
|
||||||
expect(kad.decode_peer_id(bytes)).to eq(peer_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#encode_peer' do
|
|
||||||
skip 'encodes a peer ID properly' do
|
|
||||||
bytes = "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71"
|
|
||||||
peer_id = "9B896000AEBE0B0A0ECB35457177A107"
|
|
||||||
expect(kad.encode_peer_id(peer_id)).to eq(bytes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'rex/proto/kademlia/bootstrap_request'
|
||||||
|
|
||||||
|
describe Rex::Proto::Kademlia::BootstrapRequest do
|
||||||
|
subject(:bootstrap) do
|
||||||
|
described_class.new
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'constructs properly' do
|
||||||
|
expect(bootstrap.type).to eq(Rex::Proto::Kademlia::BOOTSTRAP_REQUEST)
|
||||||
|
expect(bootstrap.body).to eq('')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_str' do
|
||||||
|
it 'packs properly' do
|
||||||
|
expect(bootstrap.to_str).to eq("\xE4\x01")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'rex/proto/kademlia/bootstrap_response'
|
||||||
|
|
||||||
|
describe Rex::Proto::Kademlia::BootstrapResponse do
|
||||||
|
describe '#from_data' do
|
||||||
|
it 'properly decodes real valid bootstrap responses' do
|
||||||
|
data = IO.read(File.join(File.dirname(__FILE__), 'kademlia_bootstrap_res.bin'))
|
||||||
|
response = described_class.from_data(data)
|
||||||
|
expect(response.peer_id).to eq('B54A83462529B21EF51FD54B956B07B0')
|
||||||
|
expect(response.tcp_port).to eq(4662)
|
||||||
|
expect(response.version).to eq(8)
|
||||||
|
# don't bother checking every peer
|
||||||
|
expect(response.peers.size).to eq(20)
|
||||||
|
peer = response.peers.first
|
||||||
|
expect(peer[:id]).to eq('B0A5518388D66BC211B0B9F75B3DCB10')
|
||||||
|
expect(peer[:ip]).to eq('149.91.116.59')
|
||||||
|
expect(peer[:tcp_port]).to eq(4882)
|
||||||
|
expect(peer[:udp_port]).to eq(4992)
|
||||||
|
expect(peer[:type]).to eq(8)
|
||||||
|
peer = response.peers.last
|
||||||
|
expect(peer[:id]).to eq('9B896000AEBE0B0A0ECB35457177A107')
|
||||||
|
expect(peer[:ip]).to eq('83.46.192.208')
|
||||||
|
expect(peer[:tcp_port]).to eq(3662)
|
||||||
|
expect(peer[:udp_port]).to eq(3672)
|
||||||
|
expect(peer[:type]).to eq(8)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not decode overly small bootstrap responses' do
|
||||||
|
expect(described_class.from_data('this is too small')).to eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not decode malformed bootstrap responses' do
|
||||||
|
expect(described_class.from_data('this is large enough but truncated')).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,49 +2,88 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
require 'rex/proto/kademlia/message'
|
require 'rex/proto/kademlia/message'
|
||||||
|
|
||||||
describe Rex::Proto::Kademlia do
|
describe Rex::Proto::Kademlia::Message do
|
||||||
subject(:kad) do
|
|
||||||
mod = Module.new
|
|
||||||
mod.extend described_class
|
|
||||||
mod
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#encode_message' do
|
context 'with a body' do
|
||||||
let(:no_body) { "\xE4\x01" }
|
let(:type) { 1 }
|
||||||
let(:body) { "\xE4\x01p2p" }
|
let(:body) { 'test' }
|
||||||
it 'properly encodes messages without a body' do
|
let(:data) { "\xE4\x01test" }
|
||||||
expect(kad.encode_message(1)).to eq("\xE4\x01")
|
|
||||||
|
subject(:message) do
|
||||||
|
described_class.new(type, body)
|
||||||
end
|
end
|
||||||
it 'properly encodes messages with a body' do
|
|
||||||
expect(kad.encode_message(1, 'p2p')).to eq("\xE4\x01p2p")
|
describe '#initialize' do
|
||||||
|
it 'constructs properly' do
|
||||||
|
expect(message.type).to eq(type)
|
||||||
|
expect(message.body).to eq(body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_str' do
|
||||||
|
it 'packs properly' do
|
||||||
|
expect(message.to_str).to eq(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#from_data' do
|
||||||
|
it 'unpacks supported messages properly' do
|
||||||
|
unpacked = described_class.from_data(data)
|
||||||
|
expect(unpacked.type).to eq(type)
|
||||||
|
expect(unpacked.body).to eq(body)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises on compressed messages' do
|
||||||
|
expect do
|
||||||
|
described_class.from_data("\xE5\x01test")
|
||||||
|
end.to raise_error(NotImplementedError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#==' do
|
||||||
|
it 'respects equality' do
|
||||||
|
expect(described_class.new(1, 'test')).to eq(described_class.new(1, 'test'))
|
||||||
|
expect(described_class.new(1, 'test')).not_to eq(described_class.new(1, 'not'))
|
||||||
|
expect(described_class.new(1, 'test')).not_to eq(described_class.new(2, 'test'))
|
||||||
|
expect(described_class.new(1, 'test')).not_to eq(described_class.new(2, 'not'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#decode_message' do
|
context 'without a body' do
|
||||||
it 'does not decode overly short messages' do
|
let(:type) { 2 }
|
||||||
expect(kad.decode_message('f')).to eq(nil)
|
let(:body) { '' }
|
||||||
|
let(:data) { "\xE4\x02" }
|
||||||
|
|
||||||
|
subject(:message) do
|
||||||
|
described_class.new(type, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not decode unknown messages' do
|
describe '#initialize' do
|
||||||
expect(kad.decode_message("this is not kademlia")).to eq(nil)
|
it 'constructs properly' do
|
||||||
|
expect(message.type).to eq(type)
|
||||||
|
expect(message.body).to eq(body)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises on compressed messages' do
|
describe '#to_str' do
|
||||||
expect do
|
it 'packs properly' do
|
||||||
kad.decode_message("\xE5\x01blahblah")
|
expect(message.to_str).to eq(data)
|
||||||
end.to raise_error(NotImplementedError)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'properly decodes valid messages without a body' do
|
describe '#from_data' do
|
||||||
type, payload = kad.decode_message("\xE4\xFF")
|
it 'unpacks supported messages properly' do
|
||||||
expect(type).to eq(0xFF)
|
unpacked = described_class.from_data(data)
|
||||||
expect(payload).to eq('')
|
expect(unpacked.type).to eq(type)
|
||||||
end
|
expect(unpacked.body).to eq(body)
|
||||||
|
end
|
||||||
|
|
||||||
it 'properly decodes valid messages wth a body' do
|
it 'raises on compressed messages' do
|
||||||
type, payload = kad.decode_message("\xE4\xFFtesttesttest")
|
expect do
|
||||||
expect(type).to eq(0xFF)
|
described_class.from_data("\xE5\x01")
|
||||||
expect(payload).to eq('testtesttest')
|
end.to raise_error(NotImplementedError)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'rex/proto/kademlia/ping'
|
||||||
|
|
||||||
|
describe Rex::Proto::Kademlia::Ping do
|
||||||
|
subject(:ping) do
|
||||||
|
described_class.new
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'constructs properly' do
|
||||||
|
expect(ping.type).to eq(Rex::Proto::Kademlia::PING)
|
||||||
|
expect(ping.body).to eq('')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_str' do
|
||||||
|
it 'packs properly' do
|
||||||
|
expect(ping.to_str).to eq("\xE4\x60")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'rex/proto/kademlia/pong'
|
||||||
|
|
||||||
|
describe Rex::Proto::Kademlia::Pong do
|
||||||
|
let(:port) { 12345 }
|
||||||
|
subject(:pong) do
|
||||||
|
described_class.new(port)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'constructs properly' do
|
||||||
|
expect(pong.type).to eq(Rex::Proto::Kademlia::PONG)
|
||||||
|
expect(pong.port).to eq(port)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#to_str' do
|
||||||
|
it 'packs properly' do
|
||||||
|
expect(pong.to_str).to eq("\xE4\x61\x39\x30")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#from_data' do
|
||||||
|
it 'unpacks supported valid pongs properly' do
|
||||||
|
unpacked = described_class.from_data("\xE4\x61\x9E\x86")
|
||||||
|
expect(unpacked.type).to eq(Rex::Proto::Kademlia::PONG)
|
||||||
|
expect(unpacked.port).to eq(34462)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not decode overly small pongs' do
|
||||||
|
expect(described_class.from_data("\xE4\x61\x01")).to eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not decode overly large pongs' do
|
||||||
|
expect(described_class.from_data("\xE4\x61\x01\x02\x03")).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
#
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'rex/proto/kademlia/util'
|
||||||
|
|
||||||
|
describe Rex::Proto::Kademlia do
|
||||||
|
|
||||||
|
describe '#decode_peer_id' do
|
||||||
|
subject(:kad) { described_class.decode_peer_id(bytes) }
|
||||||
|
let(:bytes) { "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71" }
|
||||||
|
it 'decodes a peer ID properly' do
|
||||||
|
is_expected.to eq('9B896000AEBE0B0A0ECB35457177A107')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#encode_peer' do
|
||||||
|
skip 'encodes a peer ID properly' do
|
||||||
|
bytes = "\x00\x60\x89\x9B\x0A\x0B\xBE\xAE\x45\x35\xCB\x0E\x07\xA1\x77\x71"
|
||||||
|
peer_id = "9B896000AEBE0B0A0ECB35457177A107"
|
||||||
|
expect(kad.encode_peer_id(peer_id)).to eq(bytes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue