diff --git a/lib/msf/core/auxiliary/kademlia.rb b/lib/msf/core/auxiliary/kademlia.rb index 999a74b61f..6c6b007e96 100644 --- a/lib/msf/core/auxiliary/kademlia.rb +++ b/lib/msf/core/auxiliary/kademlia.rb @@ -1,22 +1,16 @@ # -*- coding: binary -*- +require 'rex/proto/kademlia' -module Rex -module Proto -## +module Msf + +### # -# Minimal support for the newer Kademlia protocol, referred to here and often -# elsewhere as Kademlia2. It is unclear how this differs from the old protocol. +# This module provides methods for working with Kademlia # -# Protocol details are hard to come by because most documentation is academic -# in nature and glosses over the low-level network details. The best -# documents I found on the protocol are: -# -# http://gbmaster.wordpress.com/2013/05/05/botnets-surrounding-us-an-initial-focus-on-kad/ -# http://gbmaster.wordpress.com/2013/06/16/botnets-surrounding-us-sending-kademlia2_bootstrap_req-kademlia2_hello_req-and-their-strict-cousins/ -# http://gbmaster.wordpress.com/2013/11/23/botnets-surrounding-us-performing-requests-sending-out-kademlia2_req-and-asking-contact-where-art-thou/ -# -## -module Kademlia +### +module Auxiliary::Kademlia + include Rex::Proto::Kademlia + # Opcode for a BOOTSTRAP request BOOTSTRAP_REQ = 0x01 # Opcode for a BOOTSTRAP response @@ -43,15 +37,15 @@ module Kademlia # @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) - type, body = decode_message(response) + message = Message.from_data(response) # abort if this isn't a valid response - return nil unless type = BOOTSTRAP_RES - return nil unless body.size >= 23 - peer_id = decode_peer_id(body.slice!(0,16)) - tcp_port, version, num_peers = body.slice!(0,5).unpack('vCv') + 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 && body.blank? - peers = decode_bootstrap_peers(body) + 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 ] @@ -61,7 +55,7 @@ module Kademlia # # @return [String] a PING request def ping - encode_message(PING) + Message.new(PING) end # Decode a PING response, PONG @@ -69,13 +63,13 @@ module Kademlia # @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) - type, port = decode_message(response) + message = Message.from_data(response) # abort if this isn't a pong - return nil unless type == PONG + return nil unless message.type == PONG # abort if the response is too large/small - return nil unless port && port.size == 2 + return nil unless message.body && message.body.size == 2 # this should always be equivalent to the source port from which the PING was received - port.unpack('v')[0] + message.body.unpack('v')[0] end # Decode a list of peers from a BOOTSTRAP response @@ -122,4 +116,3 @@ module Kademlia # end end end -end diff --git a/lib/rex/proto/kademlia/message.rb b/lib/rex/proto/kademlia/message.rb index c189f2abe0..f7a4b45eb0 100644 --- a/lib/rex/proto/kademlia/message.rb +++ b/lib/rex/proto/kademlia/message.rb @@ -39,10 +39,10 @@ module Kademlia fail NotImplementedError, "Unable to handle #{message.length}-byte compressed Kademlia message" end return if header != STANDARD_PACKET - Message.new(type,data[2, data.length]]) + Message.new(type, data[2, data.length]) end - def to_s + def to_str [STANDARD_PACKET, @type].pack('CC') + @body end end diff --git a/modules/auxiliary/scanner/kademlia/server_info.rb b/modules/auxiliary/scanner/kademlia/server_info.rb index d3772bdac2..89f406cd71 100644 --- a/modules/auxiliary/scanner/kademlia/server_info.rb +++ b/modules/auxiliary/scanner/kademlia/server_info.rb @@ -4,12 +4,11 @@ ## require 'msf/core' -require 'rex/proto/kademlia' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::UDPScanner - include Rex::Proto::Kademlia + include Msf::Auxiliary::Kademlia def initialize(info = {}) super( diff --git a/spec/lib/rex/proto/kademlia/kademlia_bootstrap_res.bin b/spec/lib/msf/core/auxiliary/kademlia_bootstrap_res.bin similarity index 100% rename from spec/lib/rex/proto/kademlia/kademlia_bootstrap_res.bin rename to spec/lib/msf/core/auxiliary/kademlia_bootstrap_res.bin diff --git a/spec/lib/msf/core/auxiliary/kademlia_spec.rb b/spec/lib/msf/core/auxiliary/kademlia_spec.rb new file mode 100644 index 0000000000..be1c91a28e --- /dev/null +++ b/spec/lib/msf/core/auxiliary/kademlia_spec.rb @@ -0,0 +1,116 @@ +# -*- coding: binary -*- +require 'spec_helper' +require 'msf/core/auxiliary/kademlia' + +describe Msf::Auxiliary::Kademlia do + subject(:kad) do + mod = Module.new + mod.extend described_class + mod + 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 diff --git a/spec/lib/rex/proto/kademlia/message_spec.rb b/spec/lib/rex/proto/kademlia/message_spec.rb index 7ecbbeba6e..92b1c4f957 100644 --- a/spec/lib/rex/proto/kademlia/message_spec.rb +++ b/spec/lib/rex/proto/kademlia/message_spec.rb @@ -10,6 +10,8 @@ describe Rex::Proto::Kademlia do end describe '#encode_message' do + let(:no_body) { "\xE4\x01" } + let(:body) { "\xE4\x01p2p" } it 'properly encodes messages without a body' do expect(kad.encode_message(1)).to eq("\xE4\x01") end @@ -45,109 +47,4 @@ describe Rex::Proto::Kademlia do expect(payload).to eq('testtesttest') 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