Class: RubySMB::SMB2::Pipe

Inherits:
File
  • Object
show all
Includes:
Dcerpc
Defined in:
lib/ruby_smb/smb2/pipe.rb

Overview

Represents a pipe on the Remote server that we can perform various I/O operations on.

Constant Summary collapse

STATUS_CONNECTED =
0x00000003
STATUS_CLOSING =
0x00000004

Constants included from Dcerpc

Dcerpc::DCE_C_AUTHZ_DCE, Dcerpc::DCE_C_AUTHZ_NAME, Dcerpc::MAX_RECV_FRAG, Dcerpc::MAX_XMIT_FRAG, Dcerpc::RPC_C_AUTHN_DEFAULT, Dcerpc::RPC_C_AUTHN_GSS_KERBEROS, Dcerpc::RPC_C_AUTHN_GSS_NEGOTIATE, Dcerpc::RPC_C_AUTHN_GSS_SCHANNEL, Dcerpc::RPC_C_AUTHN_LEVEL_CALL, Dcerpc::RPC_C_AUTHN_LEVEL_CONNECT, Dcerpc::RPC_C_AUTHN_LEVEL_DEFAULT, Dcerpc::RPC_C_AUTHN_LEVEL_NONE, Dcerpc::RPC_C_AUTHN_LEVEL_PKT, Dcerpc::RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY, Dcerpc::RPC_C_AUTHN_NETLOGON, Dcerpc::RPC_C_AUTHN_NONE, Dcerpc::RPC_C_AUTHN_WINNT

Constants inherited from File

File::MAX_PACKET_SIZE

Instance Attribute Summary

Attributes inherited from File

#attributes, #guid, #last_access, #last_change, #last_write, #name, #size, #size_on_disk, #tree, #tree_connect_encrypt_data

Instance Method Summary collapse

Methods included from Dcerpc

#add_auth_verifier, #handle_integrity_privacy, #max_buffer_size=, #process_ntlm_type2, #recv_struct, #send_auth3, #send_packet, #set_integrity_privacy

Methods inherited from File

#append, #close, #delete, #delete_packet, #read, #read_packet, #rename, #rename_packet, #send_recv_read, #send_recv_write, #set_header_fields, #write, #write_packet

Constructor Details

#initialize(tree:, response:, name:) ⇒ Pipe

Returns a new instance of Pipe.

Raises:

  • (ArgumentError)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ruby_smb/smb2/pipe.rb', line 13

def initialize(tree:, response:, name:)
  raise ArgumentError, 'No Name Provided' if name.nil?
  case name
  when 'netlogon', '\\netlogon'
    extend RubySMB::Dcerpc::Netlogon
  when 'srvsvc', '\\srvsvc'
    extend RubySMB::Dcerpc::Srvsvc
  when 'svcctl', '\\svcctl'
    extend RubySMB::Dcerpc::Svcctl
  when 'winreg', '\\winreg'
    extend RubySMB::Dcerpc::Winreg
  when 'samr', '\\samr'
    extend RubySMB::Dcerpc::Samr
  when 'wkssvc', '\\wkssvc'
    extend RubySMB::Dcerpc::Wkssvc
  when 'netdfs', '\\netdfs'
    extend RubySMB::Dcerpc::Dfsnm
  when 'cert', '\\cert'
    extend RubySMB::Dcerpc::Icpr
  end
  super(tree: tree, response: response, name: name)
end

Instance Method Details

#bind(options = {}) ⇒ Object



36
37
38
39
40
# File 'lib/ruby_smb/smb2/pipe.rb', line 36

def bind(options={})
  @size = 1024
  @ntlm_client = @tree.client.ntlm_client
  super
end

#dcerpc_request(stub_packet, options = {}) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/ruby_smb/smb2/pipe.rb', line 98

def dcerpc_request(stub_packet, options={})
  options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
  dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
  dcerpc_request.stub.read(stub_packet.to_binary_s)
  if options[:auth_level] &&
     [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
    set_integrity_privacy(dcerpc_request, auth_level: options[:auth_level], auth_type: options[:auth_type])
  end

  ioctl_send_recv(dcerpc_request, options)
end

#ioctl_send_recv(action, options = {}) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ruby_smb/smb2/pipe.rb', line 110

def ioctl_send_recv(action, options={})
  request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
  request.ctl_code = 0x0011C017
  request.flags.is_fsctl = 0x00000001
  # TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
  request.buffer = action.to_binary_s

  ioctl_raw_response = @tree.client.send_recv(request)
  ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
  unless ioctl_response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
      packet:         ioctl_response
    )
  end
  unless [WindowsError::NTStatus::STATUS_SUCCESS,
          WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
    raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
  end

  raw_data = ioctl_response.output_data
  if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
    raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
    dcerpc_response = dcerpc_response_from_raw_response(raw_data)
    unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
      raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
    end
    if options[:auth_level] &&
       [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
      handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
    end
    stub_data = dcerpc_response.stub.to_s

    loop do
      break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
      raw_data = read(bytes: @tree.client.max_buffer_size)
      dcerpc_response = dcerpc_response_from_raw_response(raw_data)
      if options[:auth_level] &&
         [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
        handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
      end
      stub_data << dcerpc_response.stub.to_s
    end
    stub_data
  else
    dcerpc_response = dcerpc_response_from_raw_response(raw_data)
    if options[:auth_level] &&
       [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
      handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
    end
    dcerpc_response.stub.to_s
  end
end

#is_connected?Boolean

Returns True if pipe is connected, false otherwise.

Returns:

  • (Boolean)

    True if pipe is connected, false otherwise



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ruby_smb/smb2/pipe.rb', line 86

def is_connected?
  begin
    state = peek_state
  rescue RubySMB::Error::UnexpectedStatusCode => e
    if e.message == 'STATUS_FILE_CLOSED'
      return false
    end
    raise e
  end
  state == STATUS_CONNECTED
end

#peek(peek_size: 0) ⇒ RubySMB::SMB2::Packet::IoctlResponse

Performs a peek operation on the named pipe

Parameters:

  • peek_size (Integer) (defaults to: 0)

    Amount of data to peek

Returns:

Raises:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ruby_smb/smb2/pipe.rb', line 48

def peek(peek_size: 0)
  packet = RubySMB::SMB2::Packet::IoctlRequest.new
  packet.ctl_code = RubySMB::Fscc::ControlCodes::FSCTL_PIPE_PEEK
  packet.flags.is_fsctl = true
  # read at least 16 bytes for state, avail, msg_count, first_msg_len
  packet.max_output_response = 16 + peek_size
  packet = set_header_fields(packet)
  raw_response = @tree.client.send_recv(packet)
  response = RubySMB::SMB2::Packet::IoctlResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::IoctlResponse::COMMAND,
      packet:         response
    )
  end

  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end
  response
end

#peek_availableInteger

Returns The number of bytes available to be read from the pipe.

Returns:

  • (Integer)

    The number of bytes available to be read from the pipe



72
73
74
75
76
77
# File 'lib/ruby_smb/smb2/pipe.rb', line 72

def peek_available
  packet = peek
  state, avail, msg_count, first_msg_len = packet.buffer.unpack('VVVV')
  # Only 1 of these should be non-zero
  avail or first_msg_len
end

#peek_stateInteger

Returns Pipe status.

Returns:

  • (Integer)

    Pipe status



80
81
82
83
# File 'lib/ruby_smb/smb2/pipe.rb', line 80

def peek_state
  packet = peek
  packet.buffer.unpack('V')[0]
end