ruby_smb 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.simplecov +42 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +119 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +18 -0
- data/README.md +64 -0
- data/Rakefile +22 -0
- data/examples/authenticate.rb +30 -0
- data/examples/negotiate.rb +25 -0
- data/lib/ruby_smb/client/authentication.rb +236 -0
- data/lib/ruby_smb/client/negotiation.rb +126 -0
- data/lib/ruby_smb/client/signing.rb +48 -0
- data/lib/ruby_smb/client.rb +164 -0
- data/lib/ruby_smb/dispatcher/base.rb +18 -0
- data/lib/ruby_smb/dispatcher/socket.rb +53 -0
- data/lib/ruby_smb/dispatcher.rb +4 -0
- data/lib/ruby_smb/error.rb +17 -0
- data/lib/ruby_smb/field/file_time.rb +62 -0
- data/lib/ruby_smb/field/nt_status.rb +16 -0
- data/lib/ruby_smb/field/stringz16.rb +55 -0
- data/lib/ruby_smb/field.rb +7 -0
- data/lib/ruby_smb/generic_packet.rb +179 -0
- data/lib/ruby_smb/gss.rb +109 -0
- data/lib/ruby_smb/smb1/andx_block.rb +13 -0
- data/lib/ruby_smb/smb1/bit_field/capabilities.rb +39 -0
- data/lib/ruby_smb/smb1/bit_field/header_flags.rb +19 -0
- data/lib/ruby_smb/smb1/bit_field/header_flags2.rb +27 -0
- data/lib/ruby_smb/smb1/bit_field/security_mode.rb +16 -0
- data/lib/ruby_smb/smb1/bit_field.rb +10 -0
- data/lib/ruby_smb/smb1/commands.rb +9 -0
- data/lib/ruby_smb/smb1/data_block.rb +42 -0
- data/lib/ruby_smb/smb1/dialect.rb +11 -0
- data/lib/ruby_smb/smb1/packet/error_packet.rb +14 -0
- data/lib/ruby_smb/smb1/packet/negotiate_request.rb +52 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +46 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +47 -0
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +71 -0
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +48 -0
- data/lib/ruby_smb/smb1/packet.rb +12 -0
- data/lib/ruby_smb/smb1/parameter_block.rb +42 -0
- data/lib/ruby_smb/smb1/smb_header.rb +21 -0
- data/lib/ruby_smb/smb1.rb +16 -0
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +17 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_capabailities.rb +23 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_header_flags.rb +23 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_security_mode.rb +15 -0
- data/lib/ruby_smb/smb2/bit_field/smb2_security_mode_single.rb +14 -0
- data/lib/ruby_smb/smb2/bit_field.rb +11 -0
- data/lib/ruby_smb/smb2/commands.rb +25 -0
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +50 -0
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +33 -0
- data/lib/ruby_smb/smb2/packet/session_setup_request.rb +53 -0
- data/lib/ruby_smb/smb2/packet/session_setup_response.rb +38 -0
- data/lib/ruby_smb/smb2/packet.rb +10 -0
- data/lib/ruby_smb/smb2/smb2_header.rb +22 -0
- data/lib/ruby_smb/smb2.rb +12 -0
- data/lib/ruby_smb/version.rb +3 -0
- data/lib/ruby_smb.rb +22 -0
- data/ruby_smb.gemspec +38 -0
- data/spec/lib/ruby_smb/client_spec.rb +638 -0
- data/spec/lib/ruby_smb/dispatcher/dispatcher_base_spec.rb +22 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +60 -0
- data/spec/lib/ruby_smb/field/file_time_spec.rb +59 -0
- data/spec/lib/ruby_smb/field/nt_status_spec.rb +19 -0
- data/spec/lib/ruby_smb/field/stringz16_spec.rb +50 -0
- data/spec/lib/ruby_smb/generic_packet_spec.rb +58 -0
- data/spec/lib/ruby_smb/smb1/andx_block_spec.rb +41 -0
- data/spec/lib/ruby_smb/smb1/bit_field/capabilities_spec.rb +245 -0
- data/spec/lib/ruby_smb/smb1/bit_field/header_flags2_spec.rb +146 -0
- data/spec/lib/ruby_smb/smb1/bit_field/header_flags_spec.rb +102 -0
- data/spec/lib/ruby_smb/smb1/bit_field/security_mode_spec.rb +44 -0
- data/spec/lib/ruby_smb/smb1/data_block_spec.rb +26 -0
- data/spec/lib/ruby_smb/smb1/dialect_spec.rb +26 -0
- data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +39 -0
- data/spec/lib/ruby_smb/smb1/packet/negotiate_request_spec.rb +77 -0
- data/spec/lib/ruby_smb/smb1/packet/negotiate_response_extended_spec.rb +149 -0
- data/spec/lib/ruby_smb/smb1/packet/negotiate_response_spec.rb +150 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +100 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +72 -0
- data/spec/lib/ruby_smb/smb1/parameter_block_spec.rb +26 -0
- data/spec/lib/ruby_smb/smb1/smb_header_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb2/bit_field/header_flags_spec.rb +81 -0
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +28 -0
- data/spec/lib/ruby_smb/smb2/bit_field/smb2_capabilities_spec.rb +72 -0
- data/spec/lib/ruby_smb/smb2/bit_field/smb_secruity_mode_spec.rb +22 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +122 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +147 -0
- data/spec/lib/ruby_smb/smb2/packet/session_setup_request_spec.rb +79 -0
- data/spec/lib/ruby_smb/smb2/packet/session_setup_response_spec.rb +54 -0
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +127 -0
- data/spec/lib/ruby_smb_spec.rb +2 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/support/mock_socket_dispatcher.rb +8 -0
- data/spec/support/shared/examples/bit_field_single_flag.rb +14 -0
- data.tar.gz.sig +0 -0
- metadata +384 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Client
|
3
|
+
# This module holds all of the methods backing the {RubySMB::Client#negotiate} method
|
4
|
+
module Negotiation
|
5
|
+
|
6
|
+
# Handles the entire SMB Multi-Protocol Negotiation from the
|
7
|
+
# Client to the Server. It sets state on the client appropriate
|
8
|
+
# to the protocol and capabilites negotiated during the exchange.
|
9
|
+
#
|
10
|
+
# @return [void]
|
11
|
+
def negotiate
|
12
|
+
raw_response = negotiate_request
|
13
|
+
response_packet = negotiate_response(raw_response)
|
14
|
+
parse_negotiate_response(response_packet)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates and dispatches the first Negotiate Request Packet and
|
18
|
+
# returns the raw response data.
|
19
|
+
#
|
20
|
+
# @return [String] the raw binary string containing the response from the server
|
21
|
+
def negotiate_request
|
22
|
+
if smb1
|
23
|
+
request = smb1_negotiate_request
|
24
|
+
elsif smb2
|
25
|
+
request = smb2_negotiate_request
|
26
|
+
end
|
27
|
+
send_recv(request)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Takes the raw response data from the server and tries
|
31
|
+
# parse it into a valid Response packet object.
|
32
|
+
# This method currently assumes that all SMB1 will use Extended Security.
|
33
|
+
#
|
34
|
+
# @param raw_data [String] the raw binary response from the server
|
35
|
+
# @return [RubySMB::SMB1::Packet::NegotiateResponseExtended] when the response is an SMB1 Extended Security Negotiate Response Packet
|
36
|
+
# @return [RubySMB::SMB2::Packet::NegotiateResponse] when the response is an SMB2 Negotiate Response Packet
|
37
|
+
def negotiate_response(raw_data)
|
38
|
+
response = nil
|
39
|
+
if smb1
|
40
|
+
begin
|
41
|
+
packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
|
42
|
+
rescue Exception => e
|
43
|
+
raise RubySMB::Error::InvalidPacket, "Not a Valid SMB1 Negoitate Response #{e.message}"
|
44
|
+
end
|
45
|
+
if packet.valid?
|
46
|
+
response = packet
|
47
|
+
end
|
48
|
+
end
|
49
|
+
if smb2 && response.nil?
|
50
|
+
begin
|
51
|
+
packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
|
52
|
+
rescue Exception => e
|
53
|
+
raise RubySMB::Error::InvalidPacket, "Not a Valid SMB2 Negoitate Response #{e.message}"
|
54
|
+
end
|
55
|
+
response = packet
|
56
|
+
end
|
57
|
+
if response.nil?
|
58
|
+
raise RubySMB::Error::InvalidPacket, "No Valid Negotiate Response found"
|
59
|
+
end
|
60
|
+
response
|
61
|
+
end
|
62
|
+
|
63
|
+
# Sets the supported SMB Protocol and whether or not
|
64
|
+
# Signing is enabled based on the Negotiate Response Packet.
|
65
|
+
#
|
66
|
+
# @param packet [RubySMB::SMB1::Packet::NegotiateResponseExtended] if SMB1 was negotiated
|
67
|
+
# @param packet [RubySMB::SMB2::Packet::NegotiateResponse] if SMB2 was negotiated
|
68
|
+
# @return [void] This method sets state and does not return a meaningful value
|
69
|
+
def parse_negotiate_response(packet)
|
70
|
+
case packet
|
71
|
+
when RubySMB::SMB1::Packet::NegotiateResponseExtended
|
72
|
+
self.smb1 = true
|
73
|
+
self.smb2 = false
|
74
|
+
if packet.parameter_block.security_mode.security_signatures_required == 1
|
75
|
+
self.signing_required = true
|
76
|
+
else
|
77
|
+
self.signing_required = false
|
78
|
+
end
|
79
|
+
'SMB1'
|
80
|
+
when RubySMB::SMB2::Packet::NegotiateResponse
|
81
|
+
self.smb1 = false
|
82
|
+
self.smb2 = true
|
83
|
+
if packet.security_mode.signing_required == 1
|
84
|
+
self.signing_required = true
|
85
|
+
else
|
86
|
+
self.signing_required = false
|
87
|
+
end
|
88
|
+
'SMB2'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
|
94
|
+
# dialects filled in based on the protocol options set on the Client.
|
95
|
+
#
|
96
|
+
# @return [RubySMB::SMB1::Packet::NegotiateRequest] a completed SMB1 Negotiate Request packet
|
97
|
+
def smb1_negotiate_request
|
98
|
+
packet = RubySMB::SMB1::Packet::NegotiateRequest.new
|
99
|
+
# Default to always enabling Extended Security. It simplifies the Negotiation process
|
100
|
+
# while being gauranteed to work with any modern Windows system. We can get more sophisticated
|
101
|
+
# with switching this on and off at a later date if the need arises.
|
102
|
+
packet.smb_header.flags2.extended_security = 1
|
103
|
+
# There is no real good reason to ever send an SMB1 Negotiate packet
|
104
|
+
# to Negotiate strictly SMB2, but the protocol WILL support it
|
105
|
+
packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
|
106
|
+
packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
|
107
|
+
packet
|
108
|
+
end
|
109
|
+
|
110
|
+
# Create a {RubySMB::SMB2::Packet::NegotiateRequest} packet with
|
111
|
+
# the default dialect added. This will never be used when we
|
112
|
+
# may want to communicate over SMB1
|
113
|
+
#
|
114
|
+
# @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
|
115
|
+
def smb2_negotiate_request
|
116
|
+
packet = RubySMB::SMB2::Packet::NegotiateRequest.new
|
117
|
+
packet.smb2_header.message_id = self.smb2_message_id
|
118
|
+
# Increment the message id when doing SMB2
|
119
|
+
self.smb2_message_id += 1
|
120
|
+
packet.security_mode.signing_enabled = 1
|
121
|
+
packet.add_dialect(SMB2_DIALECT_DEFAULT)
|
122
|
+
packet
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Contains the methods for handling packet signing
|
5
|
+
module Signing
|
6
|
+
|
7
|
+
# The NTLM Session Key used for signing
|
8
|
+
# @!attribute [rw] session_key
|
9
|
+
# @return [String]
|
10
|
+
attr_accessor :session_key
|
11
|
+
|
12
|
+
# Take an SMB1 packet and checks to see if it should be signed.
|
13
|
+
# If signing is enabled and we have a session key already, then
|
14
|
+
# it will sign the packet appropriately.
|
15
|
+
#
|
16
|
+
# @param packet [RubySMB::GenericPacket] the packet to sign
|
17
|
+
# @return [RubySMB::GenericPacket] the packet, signed if needed
|
18
|
+
def smb1_sign(packet)
|
19
|
+
if self.signing_required && !self.session_key.empty?
|
20
|
+
packet.smb_header.security_features = self.sequence_counter
|
21
|
+
signature = OpenSSL::Digest::MD5.digest(self.session_key + packet.to_binary_s)[0,8]
|
22
|
+
packet.smb_header.security_features = signature
|
23
|
+
self.sequence_counter += 1
|
24
|
+
packet
|
25
|
+
else
|
26
|
+
packet
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Take an SMB2 packet and checks to see if it should be signed.
|
31
|
+
# If signing is enabled and we have a session key already, then
|
32
|
+
# it will sign the packet appropriately.
|
33
|
+
#
|
34
|
+
# @param packet [RubySMB::GenericPacket] the packet to sign
|
35
|
+
# @return [RubySMB::GenericPacket] the packet, signed if needed
|
36
|
+
def smb2_sign(packet)
|
37
|
+
if self.signing_required && !self.session_key.empty?
|
38
|
+
hmac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, self.session_key, packet.to_binary_s)
|
39
|
+
packet.smb2_header.signature = hmac
|
40
|
+
packet
|
41
|
+
else
|
42
|
+
packet
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module RubySMB
|
2
|
+
|
3
|
+
# Represents an SMB client capable of talking to SMB1 or SMB2 servers and handling
|
4
|
+
# all end-user client functionality.
|
5
|
+
class Client
|
6
|
+
require 'ruby_smb/client/negotiation'
|
7
|
+
require 'ruby_smb/client/authentication'
|
8
|
+
require 'ruby_smb/client/signing'
|
9
|
+
|
10
|
+
include RubySMB::Client::Negotiation
|
11
|
+
include RubySMB::Client::Authentication
|
12
|
+
include RubySMB::Client::Signing
|
13
|
+
|
14
|
+
# The Default SMB1 Dialect string used in an SMB1 Negotiate Request
|
15
|
+
SMB1_DIALECT_SMB1_DEFAULT = "NT LM 0.12"
|
16
|
+
# The Default SMB2 Dialect string used in an SMB1 Negotiate Request
|
17
|
+
SMB1_DIALECT_SMB2_DEFAULT = "SMB 2.002"
|
18
|
+
# Dialect value for SMB2 Default (Version 2.02)
|
19
|
+
SMB2_DIALECT_DEFAULT = 0x0202
|
20
|
+
|
21
|
+
|
22
|
+
# The dispatcher responsible for sending packets
|
23
|
+
# @!attribute [rw] dispatcher
|
24
|
+
# @return [RubySMB::Dispatcher::Socket]
|
25
|
+
attr_accessor :dispatcher
|
26
|
+
|
27
|
+
# The domain you're trying to authenticate to
|
28
|
+
# @!attribute [rw] domain
|
29
|
+
# @return [String]
|
30
|
+
attr_accessor :domain
|
31
|
+
|
32
|
+
# The local workstation to pretend to be
|
33
|
+
# @!attribute [rw] local_workstation
|
34
|
+
# @return [String]
|
35
|
+
attr_accessor :local_workstation
|
36
|
+
|
37
|
+
# The NTLM client used for authentication
|
38
|
+
# @!attribute [rw] ntlm_client
|
39
|
+
# @return [String]
|
40
|
+
attr_accessor :ntlm_client
|
41
|
+
|
42
|
+
# The password to authenticate with
|
43
|
+
# @!attribute [rw] password
|
44
|
+
# @return [String]
|
45
|
+
attr_accessor :password
|
46
|
+
|
47
|
+
# The Sequence Counter used for SMB1 Signing.
|
48
|
+
# It tracks the number of packets both sent and received
|
49
|
+
# since the NTLM session was initialized with the Challenge.
|
50
|
+
# @!attribute [rw] sequence_counter
|
51
|
+
# @return [Integer]
|
52
|
+
attr_accessor :sequence_counter
|
53
|
+
|
54
|
+
# The current Session ID setup by authentication
|
55
|
+
# @!attribute [rw] session_id
|
56
|
+
# @return [Integer]
|
57
|
+
attr_accessor :session_id
|
58
|
+
|
59
|
+
# Whether or not the Server requires signing
|
60
|
+
# @!attribute [rw] signing_enabled
|
61
|
+
# @return [Boolean]
|
62
|
+
attr_accessor :signing_required
|
63
|
+
|
64
|
+
# Whether or not the Client should support SMB1
|
65
|
+
# @!attribute [rw] smb1
|
66
|
+
# @return [Boolean]
|
67
|
+
attr_accessor :smb1
|
68
|
+
|
69
|
+
# Whether or not the Client should support SMB2
|
70
|
+
# @!attribute [rw] smb2
|
71
|
+
# @return [Boolean]
|
72
|
+
attr_accessor :smb2
|
73
|
+
|
74
|
+
# Tracks the current SMB2 Message ID that keeps communication in sync
|
75
|
+
# @!attribute [rw] smb2_message_id
|
76
|
+
# @return [Integer]
|
77
|
+
attr_accessor :smb2_message_id
|
78
|
+
|
79
|
+
# The username to authenticate with
|
80
|
+
# @!attribute [rw] username
|
81
|
+
# @return [String]
|
82
|
+
attr_accessor :username
|
83
|
+
|
84
|
+
# The UID set in SMB1
|
85
|
+
# @!attribute [rw] user_id
|
86
|
+
# @return [String]
|
87
|
+
attr_accessor :user_id
|
88
|
+
|
89
|
+
# @param dispatcher [RubySMB::Dispacther::Socket] the packet dispatcher to use
|
90
|
+
# @param smb1 [Boolean] whether or not to enable SMB1 support
|
91
|
+
# @param smb2 [Boolean] whether or not to enable SMB2 support
|
92
|
+
def initialize(dispatcher, smb1: true, smb2: true, username:,password:, domain:'.', local_workstation:'WORKSTATION')
|
93
|
+
raise ArgumentError, 'No Dispatcher provided' unless dispatcher.kind_of? RubySMB::Dispatcher::Base
|
94
|
+
if smb1 == false && smb2 == false
|
95
|
+
raise ArgumentError, 'You must enable at least one Protocol'
|
96
|
+
end
|
97
|
+
@dispatcher = dispatcher
|
98
|
+
@domain = domain
|
99
|
+
@local_workstation = local_workstation
|
100
|
+
@password = password.encode("utf-8")
|
101
|
+
@sequence_counter = 0
|
102
|
+
@session_id = 0x00
|
103
|
+
@session_key = ''
|
104
|
+
@signing_required = false
|
105
|
+
@smb1 = smb1
|
106
|
+
@smb2 = smb2
|
107
|
+
@username = username.encode("utf-8")
|
108
|
+
|
109
|
+
@ntlm_client = Net::NTLM::Client.new(
|
110
|
+
@username,
|
111
|
+
@password,
|
112
|
+
workstation: @local_workstation,
|
113
|
+
domain: @domain
|
114
|
+
)
|
115
|
+
|
116
|
+
@smb2_message_id = 0
|
117
|
+
end
|
118
|
+
|
119
|
+
def login(username: self.username, password: self.password, domain: self.domain, local_workstation: self.local_workstation )
|
120
|
+
@domain = domain
|
121
|
+
@local_workstation = local_workstation
|
122
|
+
@password = password.encode("utf-8")
|
123
|
+
@username = username.encode("utf-8")
|
124
|
+
|
125
|
+
@ntlm_client = Net::NTLM::Client.new(
|
126
|
+
@username,
|
127
|
+
@password,
|
128
|
+
workstation: @local_workstation,
|
129
|
+
domain: @domain
|
130
|
+
)
|
131
|
+
|
132
|
+
negotiate
|
133
|
+
authenticate
|
134
|
+
end
|
135
|
+
|
136
|
+
# Sends a packet and receives the raw response through the Dispatcher.
|
137
|
+
# It will also sign the packet if neccessary.
|
138
|
+
#
|
139
|
+
# @param packet [RubySMB::GenericPacket] the request to be sent
|
140
|
+
# @return [String] the raw response data received
|
141
|
+
def send_recv(packet)
|
142
|
+
case packet.packet_smb_version
|
143
|
+
when 'SMB1'
|
144
|
+
packet = smb1_sign(packet)
|
145
|
+
when 'SMB2'
|
146
|
+
packet = smb2_sign(packet)
|
147
|
+
else
|
148
|
+
packet = packet
|
149
|
+
end
|
150
|
+
dispatcher.send_packet(packet)
|
151
|
+
raw_response = dispatcher.recv_packet
|
152
|
+
if self.sequence_counter > 0
|
153
|
+
self.sequence_counter += 1
|
154
|
+
end
|
155
|
+
raw_response
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Provides the base class for the packet dispatcher.
|
2
|
+
class RubySMB::Dispatcher::Base
|
3
|
+
# @param packet [#length]
|
4
|
+
# @return [Fixnum] NBSS header to go in front of `packet`
|
5
|
+
def nbss(packet)
|
6
|
+
[packet.do_num_bytes].pack('N')
|
7
|
+
end
|
8
|
+
|
9
|
+
# @abstract
|
10
|
+
def send_packet(packet)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
# @abstract
|
15
|
+
def recv_packet
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# This class provides a wrapper around a Socket for the packet Dispatcher.
|
4
|
+
# It allows for dependency injection of different Socket implementations.
|
5
|
+
class RubySMB::Dispatcher::Socket < RubySMB::Dispatcher::Base
|
6
|
+
# The underlying socket that we select on
|
7
|
+
# @!attribute [rw] tcp_socket
|
8
|
+
# @return [IO]
|
9
|
+
attr_accessor :tcp_socket
|
10
|
+
|
11
|
+
# @param tcp_socket [IO]
|
12
|
+
def initialize(tcp_socket)
|
13
|
+
@tcp_socket = tcp_socket
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param host [String] passed to TCPSocket.new
|
17
|
+
# @param port [Fixnum] passed to TCPSocket.new
|
18
|
+
def self.connect(host, port: 445, socket: TCPSocket.new(host, port))
|
19
|
+
new(socket)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param packet [SMB2::Packet,#to_s]
|
23
|
+
# @return [void]
|
24
|
+
def send_packet(packet)
|
25
|
+
data = nbss(packet) + packet.to_binary_s
|
26
|
+
bytes_written = 0
|
27
|
+
while bytes_written < data.size
|
28
|
+
bytes_written += @tcp_socket.write(data[bytes_written..-1])
|
29
|
+
end
|
30
|
+
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
# Read a packet off the wire and parse it into a string
|
35
|
+
# Throw Error::NetBiosSessionService if there's an error reading the first 4 bytes,
|
36
|
+
# which are assumed to be the NetBiosSessionService header.
|
37
|
+
# @return [String]
|
38
|
+
# @todo should return SMB2::Packet
|
39
|
+
def recv_packet
|
40
|
+
IO.select([@tcp_socket])
|
41
|
+
nbss_header = @tcp_socket.read(4) # Length of NBSS header. TODO: remove to a constant
|
42
|
+
if nbss_header.nil?
|
43
|
+
raise ::RubySMB::Error::NetBiosSessionService, 'NBSS Header is missing'
|
44
|
+
else
|
45
|
+
length = nbss_header.unpack('N').first
|
46
|
+
end
|
47
|
+
IO.select([@tcp_socket])
|
48
|
+
data = @tcp_socket.read(length)
|
49
|
+
data << @tcp_socket.read(length - data.length) while data.length < length
|
50
|
+
|
51
|
+
data
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RubySMB::Error
|
2
|
+
# Raised when there is a length or formatting issue with an ASN1-encoded string
|
3
|
+
# @see https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
|
4
|
+
# @todo Find an SMB-specific link for ASN1 above
|
5
|
+
class ASN1Encoding < StandardError; end
|
6
|
+
|
7
|
+
# Raised when there is a problem with communication over NetBios Session Service
|
8
|
+
# @see https://wiki.wireshark.org/NetBIOS/NBSS
|
9
|
+
class NetBiosSessionService < StandardError; end
|
10
|
+
|
11
|
+
# Raised when trying to parse raw binary into a Packet and the data
|
12
|
+
# is invalid.
|
13
|
+
class InvalidPacket < StandardError; end
|
14
|
+
|
15
|
+
# Raised when a response packet has a NTStatus code that was unexpected.
|
16
|
+
class UnexpectedStatusCode < StandardError; end
|
17
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Field
|
3
|
+
# Represents a Windows FILETIME structure as defined in
|
4
|
+
# [FILETIME structure](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx)
|
5
|
+
class FileTime < BinData::Primitive
|
6
|
+
# Difference between the Windows and Unix epochs, in 100ns intervals
|
7
|
+
EPOCH_DIFF_100NS = 116_444_736_000_000_000
|
8
|
+
NS_MULTIPLIER = 10_000_000
|
9
|
+
endian :little
|
10
|
+
uint64 :val
|
11
|
+
|
12
|
+
# Gets the value of the field
|
13
|
+
#
|
14
|
+
# @return [BinData::Bit64] the 64-bit value of the field
|
15
|
+
def get
|
16
|
+
val
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets the value of the field from a DateTime,Time,Fixnum, or object
|
20
|
+
# that can be converted to an integer. Datetime and Time objects get
|
21
|
+
# converted to account for the Windows/Unix Epoch difference. Any other
|
22
|
+
# parameter passed in will be assumed to already be correct.
|
23
|
+
#
|
24
|
+
# @param value [DateTime,Time,Fixnum,#to_i] the value to set
|
25
|
+
# @return
|
26
|
+
def set(value)
|
27
|
+
case value
|
28
|
+
when DateTime
|
29
|
+
set(value.to_time)
|
30
|
+
when Time
|
31
|
+
time_int = value.to_i
|
32
|
+
time_int *= NS_MULTIPLIER
|
33
|
+
adjusted_epoch = time_int + EPOCH_DIFF_100NS
|
34
|
+
set(adjusted_epoch)
|
35
|
+
when Fixnum
|
36
|
+
self.val = value
|
37
|
+
else
|
38
|
+
self.val = value.to_i
|
39
|
+
end
|
40
|
+
val
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the value of the field as a {DateTime}
|
44
|
+
#
|
45
|
+
# @return [DateTime] the {DateTime} representation of the current value
|
46
|
+
def to_datetime
|
47
|
+
time = to_time
|
48
|
+
time.to_datetime
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the value of the field as a {Time}
|
52
|
+
#
|
53
|
+
# @return [Time] the {Time} representation of the current value
|
54
|
+
def to_time
|
55
|
+
windows_int = val
|
56
|
+
adjusted_epoch = windows_int - EPOCH_DIFF_100NS
|
57
|
+
unix_int = adjusted_epoch / NS_MULTIPLIER
|
58
|
+
Time.at unix_int
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'windows_error/nt_status'
|
2
|
+
|
3
|
+
module RubySMB
|
4
|
+
module Field
|
5
|
+
# Represents an NTStatus code as defined in
|
6
|
+
# [2.3.1 NTSTATUS values](https://msdn.microsoft.com/en-us/library/cc704588.aspx)
|
7
|
+
class NtStatus < BinData::Uint32le
|
8
|
+
# Returns a meaningful error code parsed from the numeric value
|
9
|
+
#
|
10
|
+
# @return [WindowsError::ErrorCode] the ErrorCode object for this code
|
11
|
+
def to_nt_status
|
12
|
+
WindowsError::NTStatus.find_by_retval(value).first
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Field
|
3
|
+
# Represents a NULL-Terminated String in UTF-16
|
4
|
+
class Stringz16 < BinData::Stringz
|
5
|
+
|
6
|
+
def assign(val)
|
7
|
+
super(binary_string(val.encode("utf-16le")))
|
8
|
+
end
|
9
|
+
|
10
|
+
def snapshot
|
11
|
+
# override to always remove trailing zero bytes
|
12
|
+
result = _value
|
13
|
+
result
|
14
|
+
result = trim_and_zero_terminate(result)
|
15
|
+
result.chomp("\0\0").force_encoding("utf-16le")
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def append_zero_byte_if_needed!(str)
|
21
|
+
if str.length == 0 || !(str.end_with?("\0\0"))
|
22
|
+
str << "\0\0"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Override parent on {BinData::Stringz} to use
|
27
|
+
# a double NULL-byte instead of a single NULL-byte
|
28
|
+
# as a terminator
|
29
|
+
# @see BinData::Stringz
|
30
|
+
def read_and_return_value(io)
|
31
|
+
max_length = eval_parameter(:max_length)
|
32
|
+
str = ''
|
33
|
+
i = 0
|
34
|
+
ch = nil
|
35
|
+
|
36
|
+
# read until double NULL-byte or we have read in the max number of bytes
|
37
|
+
while (ch != "\0\0") && (i != max_length)
|
38
|
+
ch = io.readbytes(2)
|
39
|
+
str << ch
|
40
|
+
i += 2
|
41
|
+
end
|
42
|
+
|
43
|
+
trim_and_zero_terminate(str)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Override parent method of #truncate_after_first_zero_byte! on
|
47
|
+
# {BinData::Stringz} to use two consecutive NULL-bytes as the terimnator
|
48
|
+
# instead of a single NULL-nyte.
|
49
|
+
# @see BinData::Stringz
|
50
|
+
def truncate_after_first_zero_byte!(str)
|
51
|
+
str.sub!(/([^\0]*\0\0\0).*/, '\1')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|