Skip to content

Commit 5d2c02f

Browse files
committed
Initial commit of more OO version of Rex/Aux Kademlia support
1 parent 94e5ba1 commit 5d2c02f

File tree

3 files changed

+145
-121
lines changed

3 files changed

+145
-121
lines changed

lib/msf/core/auxiliary/kademlia.rb

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# -*- coding: binary -*-
2+
3+
module Rex
4+
module Proto
5+
##
6+
#
7+
# Minimal support for the newer Kademlia protocol, referred to here and often
8+
# elsewhere as Kademlia2. It is unclear how this differs from the old protocol.
9+
#
10+
# Protocol details are hard to come by because most documentation is academic
11+
# in nature and glosses over the low-level network details. The best
12+
# documents I found on the protocol are:
13+
#
14+
# http://gbmaster.wordpress.com/2013/05/05/botnets-surrounding-us-an-initial-focus-on-kad/
15+
# http://gbmaster.wordpress.com/2013/06/16/botnets-surrounding-us-sending-kademlia2_bootstrap_req-kademlia2_hello_req-and-their-strict-cousins/
16+
# http://gbmaster.wordpress.com/2013/11/23/botnets-surrounding-us-performing-requests-sending-out-kademlia2_req-and-asking-contact-where-art-thou/
17+
#
18+
##
19+
module Kademlia
20+
# Opcode for a BOOTSTRAP request
21+
BOOTSTRAP_REQ = 0x01
22+
# Opcode for a BOOTSTRAP response
23+
BOOTSTRAP_RES = 0x09
24+
# Opcode for a PING request
25+
PING = 0x60
26+
# Opcode for a PING response
27+
PONG = 0x61
28+
# The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
29+
# peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
30+
# and version (1 byte)
31+
BOOTSTRAP_PEER_SIZE = 25
32+
33+
# Builds a BOOTSTRAP request
34+
#
35+
# @return [String] a BOOTSTRAP request
36+
def bootstrap
37+
Message.new(BOOTSTRAP_REQ)
38+
end
39+
40+
# Decodes a BOOTSTRAP response
41+
#
42+
# @param response [String] the response to decode
43+
# @return [Array] the discovered peer ID, TCP port, version and a list of peers
44+
# if the response if valid, nil otherwise
45+
def decode_bootstrap_res(response)
46+
type, body = decode_message(response)
47+
# abort if this isn't a valid response
48+
return nil unless type = BOOTSTRAP_RES
49+
return nil unless body.size >= 23
50+
peer_id = decode_peer_id(body.slice!(0,16))
51+
tcp_port, version, num_peers = body.slice!(0,5).unpack('vCv')
52+
# protocol says there are no peers and the body confirms this, so just return with no peers
53+
return [ tcp_port, version, []] if num_peers == 0 && body.blank?
54+
peers = decode_bootstrap_peers(body)
55+
# abort if the peer data was invalid
56+
return nil unless peers
57+
[ peer_id, tcp_port, version, peers ]
58+
end
59+
60+
# Builds a PING request
61+
#
62+
# @return [String] a PING request
63+
def ping
64+
encode_message(PING)
65+
end
66+
67+
# Decode a PING response, PONG
68+
#
69+
# @param response [String] the response to decode
70+
# @return [Integer] the source port from the PING response if the response is valid, nil otherwise
71+
def decode_pong(response)
72+
type, port = decode_message(response)
73+
# abort if this isn't a pong
74+
return nil unless type == PONG
75+
# abort if the response is too large/small
76+
return nil unless port && port.size == 2
77+
# this should always be equivalent to the source port from which the PING was received
78+
port.unpack('v')[0]
79+
end
80+
81+
# Decode a list of peers from a BOOTSTRAP response
82+
#
83+
# @param peers_data [String] the peers data from a BOOTSTRAP response
84+
# @return [Array] a list of the peers and their associated metadata extracted
85+
# from the response if valid, nil otherwise
86+
def decode_bootstrap_peers(peers_data)
87+
# sanity check total size
88+
return nil unless peers_data.size % BOOTSTRAP_PEER_SIZE == 0
89+
peers = []
90+
until peers_data.blank?
91+
peers << decode_bootstrap_peer(peers_data.slice!(0, BOOTSTRAP_PEER_SIZE))
92+
end
93+
peers
94+
end
95+
96+
# Decodes a single set of peer data from a BOOTSTRAP reseponse
97+
#
98+
# @param peer-data [String] the peer data for one peer from a BOOSTRAP response
99+
# @return [Array] the peer ID, IPv4 addresss, UDP port, TCP port and version of this peer
100+
def decode_bootstrap_peer(peer_data)
101+
# sanity check the size of this peer's data
102+
return nil unless peer_data.size == BOOTSTRAP_PEER_SIZE
103+
# TODO; interpret this properly
104+
peer_id = peer_data.slice!(0, 16)
105+
ip, udp_port, tcp_port, version = peer_data.unpack('VvvC')
106+
[ decode_peer_id(peer_id), Rex::Socket.addr_itoa(ip), udp_port, tcp_port, version ]
107+
end
108+
109+
# Decodes an on-the-wire representation of a Kademlia peer to its 16-character hex equivalent
110+
#
111+
# @param bytes [String] the on-the-wire representation of a Kademlia peer
112+
# @return [String] the peer ID if valid, nil otherwise
113+
def decode_peer_id(bytes)
114+
peer_id = 0
115+
return nil unless bytes.size == 16
116+
bytes.unpack('VVVV').map { |p| peer_id <<= 32; peer_id ^= p; }
117+
peer_id.to_s(16).upcase
118+
end
119+
120+
# TODO
121+
# def encode_peer_id(id)
122+
# end
123+
end
124+
end
125+
end

lib/msf/core/auxiliary/mixins.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
require 'msf/core/auxiliary/login'
2020
require 'msf/core/auxiliary/rservices'
2121
require 'msf/core/auxiliary/cisco'
22+
require 'msf/core/auxiliary/kademlia'
2223
require 'msf/core/auxiliary/nmap'
2324
require 'msf/core/auxiliary/natpmp'
2425
require 'msf/core/auxiliary/iax2'

lib/rex/proto/kademlia/message.rb

Lines changed: 19 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -21,133 +21,31 @@ module Kademlia
2121
STANDARD_PACKET = 0xE4
2222
# The header that compressed Kad messages use, which is currently unsupported
2323
COMPRESSED_PACKET = 0xE5
24-
# Opcode for a BOOTSTRAP request
25-
BOOTSTRAP_REQ = 0x01
26-
# Opcode for a BOOTSTRAP response
27-
BOOTSTRAP_RES = 0x09
28-
# Opcode for a PING request
29-
PING = 0x60
30-
# Opcode for a PING response
31-
PONG = 0x61
32-
# The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
33-
# peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
34-
# and version (1 byte)
35-
BOOTSTRAP_PEER_SIZE = 25
3624

37-
# Decodes a Kademlia message.
38-
#
39-
# @param message [String] the message to decode
40-
# @return [Array] the message type and body if valid, nil otherwise
41-
def decode_message(message)
42-
# minimum size is header (1) + type (1) + stuff (0+)
43-
return if message.length < 2
44-
header, type = message.unpack('CC')
45-
if header == COMPRESSED_PACKET
46-
fail NotImplementedError, "Unable to handle #{message.length}-byte compressed Kademlia message"
47-
end
48-
return if header != STANDARD_PACKET
49-
[type, message[2, message.length]]
50-
end
25+
class Message
26+
attr_accessor :type, :body
5127

52-
# Encodes a Kademlia message.
53-
#
54-
# @param type [String] the message type
55-
# @param body [String] the message body
56-
# @return [String] the encoded Kademlia message
57-
def encode_message(type, body = '')
58-
[STANDARD_PACKET, type].pack('CC') + body
59-
end
60-
61-
# Builds a BOOTSTRAP request
62-
#
63-
# @return [String] a BOOTSTRAP request
64-
def bootstrap
65-
encode_message(BOOTSTRAP_REQ)
66-
end
67-
68-
# Decodes a BOOTSTRAP response
69-
#
70-
# @param response [String] the response to decode
71-
# @return [Array] the discovered peer ID, TCP port, version and a list of peers
72-
# if the response if valid, nil otherwise
73-
def decode_bootstrap_res(response)
74-
type, body = decode_message(response)
75-
# abort if this isn't a valid response
76-
return nil unless type = BOOTSTRAP_RES
77-
return nil unless body.size >= 23
78-
peer_id = decode_peer_id(body.slice!(0,16))
79-
tcp_port, version, num_peers = body.slice!(0,5).unpack('vCv')
80-
# protocol says there are no peers and the body confirms this, so just return with no peers
81-
return [ tcp_port, version, []] if num_peers == 0 && body.blank?
82-
peers = decode_bootstrap_peers(body)
83-
# abort if the peer data was invalid
84-
return nil unless peers
85-
[ peer_id, tcp_port, version, peers ]
86-
end
87-
88-
# Builds a PING request
89-
#
90-
# @return [String] a PING request
91-
def ping
92-
encode_message(PING)
93-
end
94-
95-
# Decode a PING response, PONG
96-
#
97-
# @param response [String] the response to decode
98-
# @return [Integer] the source port from the PING response if the response is valid, nil otherwise
99-
def decode_pong(response)
100-
type, port = decode_message(response)
101-
# abort if this isn't a pong
102-
return nil unless type == PONG
103-
# abort if the response is too large/small
104-
return nil unless port && port.size == 2
105-
# this should always be equivalent to the source port from which the PING was received
106-
port.unpack('v')[0]
107-
end
108-
109-
# Decode a list of peers from a BOOTSTRAP response
110-
#
111-
# @param peers_data [String] the peers data from a BOOTSTRAP response
112-
# @return [Array] a list of the peers and their associated metadata extracted
113-
# from the response if valid, nil otherwise
114-
def decode_bootstrap_peers(peers_data)
115-
# sanity check total size
116-
return nil unless peers_data.size % BOOTSTRAP_PEER_SIZE == 0
117-
peers = []
118-
until peers_data.blank?
119-
peers << decode_bootstrap_peer(peers_data.slice!(0, BOOTSTRAP_PEER_SIZE))
28+
# @param type [String] the message type
29+
# @param body [String] the message body
30+
def initialize(type, body = '')
31+
@type = type
32+
@body = body
12033
end
121-
peers
122-
end
12334

124-
# Decodes a single set of peer data from a BOOTSTRAP reseponse
125-
#
126-
# @param peer-data [String] the peer data for one peer from a BOOSTRAP response
127-
# @return [Array] the peer ID, IPv4 addresss, UDP port, TCP port and version of this peer
128-
def decode_bootstrap_peer(peer_data)
129-
# sanity check the size of this peer's data
130-
return nil unless peer_data.size == BOOTSTRAP_PEER_SIZE
131-
# TODO; interpret this properly
132-
peer_id = peer_data.slice!(0, 16)
133-
ip, udp_port, tcp_port, version = peer_data.unpack('VvvC')
134-
[ decode_peer_id(peer_id), Rex::Socket.addr_itoa(ip), udp_port, tcp_port, version ]
135-
end
35+
def self.from_data(data)
36+
return if data.length < 2
37+
header, type = data.unpack('CC')
38+
if header == COMPRESSED_PACKET
39+
fail NotImplementedError, "Unable to handle #{message.length}-byte compressed Kademlia message"
40+
end
41+
return if header != STANDARD_PACKET
42+
Message.new(type,data[2, data.length]])
43+
end
13644

137-
# Decodes an on-the-wire representation of a Kademlia peer to its 16-character hex equivalent
138-
#
139-
# @param bytes [String] the on-the-wire representation of a Kademlia peer
140-
# @return [String] the peer ID if valid, nil otherwise
141-
def decode_peer_id(bytes)
142-
peer_id = 0
143-
return nil unless bytes.size == 16
144-
bytes.unpack('VVVV').map { |p| peer_id <<= 32; peer_id ^= p; }
145-
peer_id.to_s(16).upcase
45+
def to_s
46+
[STANDARD_PACKET, @type].pack('CC') + @body
47+
end
14648
end
147-
148-
# TODO
149-
# def encode_peer_id(id)
150-
# end
15149
end
15250
end
15351
end

0 commit comments

Comments
 (0)