ruby_smb 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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