ruby_smb 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +3 -0
  5. data/.simplecov +42 -0
  6. data/.travis.yml +5 -0
  7. data/.yardopts +1 -0
  8. data/CONTRIBUTING.md +119 -0
  9. data/Gemfile +13 -0
  10. data/LICENSE.txt +18 -0
  11. data/README.md +64 -0
  12. data/Rakefile +22 -0
  13. data/examples/authenticate.rb +30 -0
  14. data/examples/negotiate.rb +25 -0
  15. data/lib/ruby_smb/client/authentication.rb +236 -0
  16. data/lib/ruby_smb/client/negotiation.rb +126 -0
  17. data/lib/ruby_smb/client/signing.rb +48 -0
  18. data/lib/ruby_smb/client.rb +164 -0
  19. data/lib/ruby_smb/dispatcher/base.rb +18 -0
  20. data/lib/ruby_smb/dispatcher/socket.rb +53 -0
  21. data/lib/ruby_smb/dispatcher.rb +4 -0
  22. data/lib/ruby_smb/error.rb +17 -0
  23. data/lib/ruby_smb/field/file_time.rb +62 -0
  24. data/lib/ruby_smb/field/nt_status.rb +16 -0
  25. data/lib/ruby_smb/field/stringz16.rb +55 -0
  26. data/lib/ruby_smb/field.rb +7 -0
  27. data/lib/ruby_smb/generic_packet.rb +179 -0
  28. data/lib/ruby_smb/gss.rb +109 -0
  29. data/lib/ruby_smb/smb1/andx_block.rb +13 -0
  30. data/lib/ruby_smb/smb1/bit_field/capabilities.rb +39 -0
  31. data/lib/ruby_smb/smb1/bit_field/header_flags.rb +19 -0
  32. data/lib/ruby_smb/smb1/bit_field/header_flags2.rb +27 -0
  33. data/lib/ruby_smb/smb1/bit_field/security_mode.rb +16 -0
  34. data/lib/ruby_smb/smb1/bit_field.rb +10 -0
  35. data/lib/ruby_smb/smb1/commands.rb +9 -0
  36. data/lib/ruby_smb/smb1/data_block.rb +42 -0
  37. data/lib/ruby_smb/smb1/dialect.rb +11 -0
  38. data/lib/ruby_smb/smb1/packet/error_packet.rb +14 -0
  39. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +52 -0
  40. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +46 -0
  41. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +47 -0
  42. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +71 -0
  43. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +48 -0
  44. data/lib/ruby_smb/smb1/packet.rb +12 -0
  45. data/lib/ruby_smb/smb1/parameter_block.rb +42 -0
  46. data/lib/ruby_smb/smb1/smb_header.rb +21 -0
  47. data/lib/ruby_smb/smb1.rb +16 -0
  48. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +17 -0
  49. data/lib/ruby_smb/smb2/bit_field/smb2_capabailities.rb +23 -0
  50. data/lib/ruby_smb/smb2/bit_field/smb2_header_flags.rb +23 -0
  51. data/lib/ruby_smb/smb2/bit_field/smb2_security_mode.rb +15 -0
  52. data/lib/ruby_smb/smb2/bit_field/smb2_security_mode_single.rb +14 -0
  53. data/lib/ruby_smb/smb2/bit_field.rb +11 -0
  54. data/lib/ruby_smb/smb2/commands.rb +25 -0
  55. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +50 -0
  56. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +33 -0
  57. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +53 -0
  58. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +38 -0
  59. data/lib/ruby_smb/smb2/packet.rb +10 -0
  60. data/lib/ruby_smb/smb2/smb2_header.rb +22 -0
  61. data/lib/ruby_smb/smb2.rb +12 -0
  62. data/lib/ruby_smb/version.rb +3 -0
  63. data/lib/ruby_smb.rb +22 -0
  64. data/ruby_smb.gemspec +38 -0
  65. data/spec/lib/ruby_smb/client_spec.rb +638 -0
  66. data/spec/lib/ruby_smb/dispatcher/dispatcher_base_spec.rb +22 -0
  67. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +60 -0
  68. data/spec/lib/ruby_smb/field/file_time_spec.rb +59 -0
  69. data/spec/lib/ruby_smb/field/nt_status_spec.rb +19 -0
  70. data/spec/lib/ruby_smb/field/stringz16_spec.rb +50 -0
  71. data/spec/lib/ruby_smb/generic_packet_spec.rb +58 -0
  72. data/spec/lib/ruby_smb/smb1/andx_block_spec.rb +41 -0
  73. data/spec/lib/ruby_smb/smb1/bit_field/capabilities_spec.rb +245 -0
  74. data/spec/lib/ruby_smb/smb1/bit_field/header_flags2_spec.rb +146 -0
  75. data/spec/lib/ruby_smb/smb1/bit_field/header_flags_spec.rb +102 -0
  76. data/spec/lib/ruby_smb/smb1/bit_field/security_mode_spec.rb +44 -0
  77. data/spec/lib/ruby_smb/smb1/data_block_spec.rb +26 -0
  78. data/spec/lib/ruby_smb/smb1/dialect_spec.rb +26 -0
  79. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +39 -0
  80. data/spec/lib/ruby_smb/smb1/packet/negotiate_request_spec.rb +77 -0
  81. data/spec/lib/ruby_smb/smb1/packet/negotiate_response_extended_spec.rb +149 -0
  82. data/spec/lib/ruby_smb/smb1/packet/negotiate_response_spec.rb +150 -0
  83. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +100 -0
  84. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +72 -0
  85. data/spec/lib/ruby_smb/smb1/parameter_block_spec.rb +26 -0
  86. data/spec/lib/ruby_smb/smb1/smb_header_spec.rb +96 -0
  87. data/spec/lib/ruby_smb/smb2/bit_field/header_flags_spec.rb +81 -0
  88. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +28 -0
  89. data/spec/lib/ruby_smb/smb2/bit_field/smb2_capabilities_spec.rb +72 -0
  90. data/spec/lib/ruby_smb/smb2/bit_field/smb_secruity_mode_spec.rb +22 -0
  91. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +122 -0
  92. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +147 -0
  93. data/spec/lib/ruby_smb/smb2/packet/session_setup_request_spec.rb +79 -0
  94. data/spec/lib/ruby_smb/smb2/packet/session_setup_response_spec.rb +54 -0
  95. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +127 -0
  96. data/spec/lib/ruby_smb_spec.rb +2 -0
  97. data/spec/spec_helper.rb +100 -0
  98. data/spec/support/mock_socket_dispatcher.rb +8 -0
  99. data/spec/support/shared/examples/bit_field_single_flag.rb +14 -0
  100. data.tar.gz.sig +0 -0
  101. metadata +384 -0
  102. 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,4 @@
1
+ module RubySMB::Dispatcher
2
+ require 'ruby_smb/dispatcher/base'
3
+ require 'ruby_smb/dispatcher/socket'
4
+ 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
@@ -0,0 +1,7 @@
1
+ module RubySMB
2
+ module Field
3
+ require 'ruby_smb/field/file_time'
4
+ require 'ruby_smb/field/stringz16'
5
+ require 'ruby_smb/field/nt_status'
6
+ end
7
+ end