Skip to content

Commit

Permalink
Land #270, Adding NetrWkstaUserEnum implementation
Browse files Browse the repository at this point in the history
Adding NetrWkstaUserEnum implementation
  • Loading branch information
smcintyre-r7 committed Sep 11, 2024
2 parents 682271e + eaf2a64 commit ce82cb4
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 17 deletions.
3 changes: 2 additions & 1 deletion lib/ruby_smb/dcerpc/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ class Request < BinData::Record
string :default
end
choice 'Wkssvc', selection: -> { opnum } do
netr_wksta_get_info_request Wkssvc::NETR_WKSTA_GET_INFO
netr_wksta_get_info_request Wkssvc::NETR_WKSTA_GET_INFO
netr_wksta_user_enum_request Wkssvc::NETR_WKSTA_USER_ENUM
string :default
end
choice 'Epm', selection: -> { opnum } do
Expand Down
121 changes: 118 additions & 3 deletions lib/ruby_smb/dcerpc/wkssvc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ module Wkssvc
VER_MINOR = 0

# Operation numbers
NETR_WKSTA_GET_INFO = 0x0000
NETR_WKSTA_GET_INFO = 0x0000
NETR_WKSTA_USER_ENUM = 0x0002

PLATFORM_ID = {
0x0000012C => "DOS",
Expand All @@ -23,24 +24,100 @@ module Wkssvc
WKSTA_INFO_102 = 0x00000066
#TODO: WKSTA_INFO_502 = 0x000001F6

# User Enum Information Level
WKSTA_USER_INFO_0 = 0x00000000
WKSTA_USER_INFO_1 = 0x00000001

# [2.2.2.1 WKSSVC_IDENTIFY_HANDLE](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/9ef94a11-0e5c-49d7-9ac7-68d6f03565de)
class WkssvcIdentifyHandle < Ndr::NdrWideStringzPtr; end

# [2.2.5.9 WKSTA_USER_INFO_0](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/b7c53c6f-8b92-4e5d-9a2e-6462cb4ef1ac)
class WkstaUserInfo0 < Ndr::NdrStruct
default_parameter byte_align: 4
endian :little

ndr_wide_stringz_ptr :wkui0_username
end

class WkstaUserInfo0ArrayPtr < Ndr::NdrConfArray
default_parameter type: :wksta_user_info0
extend Ndr::PointerClassPlugin
end

# [2.2.5.12 WKSTA_USER_INFO_0_CONTAINER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/0b0cff8f-09bc-43a8-b0d3-88f0bf7e3664)
class WkstaUserInfo0Container < Ndr::NdrStruct
default_parameter byte_align: 4
endian :little

ndr_uint32 :wkui0_entries_read
wksta_user_info0_array_ptr :wkui0_buffer
end

class PwkstaUserInfo0Container < WkstaUserInfo0Container
extend Ndr::PointerClassPlugin
end

# [2.2.5.10 WKSTA_USER_INFO_1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/c37b9606-866f-40ac-9490-57b8334968e2)
class WkstaUserInfo1 < Ndr::NdrStruct
default_parameter byte_align: 4
endian :little

ndr_wide_stringz_ptr :wkui1_username
ndr_wide_stringz_ptr :wkui1_logon_domain
ndr_wide_stringz_ptr :wkui1_oth_domains
ndr_wide_stringz_ptr :wkui1_logon_server
end

class WkstaUserInfo1ArrayPtr < Ndr::NdrConfArray
default_parameter type: :wksta_user_info1
extend Ndr::PointerClassPlugin
end

# [2.2.5.13 WKSTA_USER_INFO_1_CONTAINER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/22a813e4-fc7d-4fe3-a6d6-78debfd2c0c9)
class WkstaUserInfo1Container < Ndr::NdrStruct
default_parameter byte_align: 4
endian :little

ndr_uint32 :wkui1_entries_read
wksta_user_info1_array_ptr :wkui1_buffer
end

class PwkstaUserInfo1Container < WkstaUserInfo1Container
extend Ndr::PointerClassPlugin
end

# [2.2.5.14 WKSTA_USER_ENUM_STRUCT](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4041455a-52be-4389-a4fc-82fea3cb3160)
class WkstaUserEnumStructure < Ndr::NdrStruct
default_parameter byte_align: 4
endian :little

ndr_uint32 :level
ndr_uint32 :tag, value: -> { self.level }
choice :info, selection: :level, byte_align: 4 do
pwksta_user_info0_container WKSTA_USER_INFO_0
pwksta_user_info1_container WKSTA_USER_INFO_1
end
end

require 'ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request'
require 'ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response'
require 'ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_request'
require 'ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_response'

# Returns details about a computer environment, including
# platform-specific information, the names of the domain and local
# computer, and the operating system version.
#
# @param server_name [optional, String] String that identifies the server (optional
# since it is ignored by the server)
# @param server_name [optional, Integer] The information level of the data (default: WKSTA_INFO_100)
# @param level [optional, Integer] The information level of the data (default: WKSTA_INFO_100)
# @return [RubySMB::Dcerpc::Wkssvc::WkstaInfo100, RubySMB::Dcerpc::Wkssvc::WkstaInfo101,
# RubySMB::Dcerpc::Wkssvc::WkstaInfo102] The structure containing the requested information
# @raise [RubySMB::Dcerpc::Error::InvalidPacket] if the response is not a
# NetrWkstaGetInfoResponse packet
# @raise [RubySMB::Dcerpc::Error::WkssvcError] if the response error status
# is not STATUS_SUCCESS
def netr_wksta_get_info(server_name: "\x00", level: WKSTA_INFO_100)
def netr_wksta_get_info(server_name: '', level: WKSTA_INFO_100)
wkst_netr_wksta_get_info_request = NetrWkstaGetInfoRequest.new(
server_name: server_name,
level: level
Expand All @@ -59,6 +136,44 @@ def netr_wksta_get_info(server_name: "\x00", level: WKSTA_INFO_100)
wkst_netr_wksta_get_info_response.wksta_info.info
end

# Returns details about users who are currently active on a remote computer.
#
# @param server_name [optional, String] String that identifies the server (optional
# since it is ignored by the server)
# @param level [optional, Integer] The information level of the data (default: WKSTA_USER_INFO_0)
# @return [RubySMB::Dcerpc::Wkssvc::WkstaUserInfo0, RubySMB::Dcerpc::Wkssvc::WkstaUserInfo1]
# The structure containing the requested information
# @raise [RubySMB::Dcerpc::Error::InvalidPacket] if the response is not a
# NetrWkstaGetInfoResponse packet
# @raise [RubySMB::Dcerpc::Error::WkssvcError] if the response error status
# is not STATUS_SUCCESS
def netr_wksta_user_enum(server_name: '', level: WKSTA_USER_INFO_0)
wkst_netr_wksta_enum_user_request = NetrWkstaUserEnumRequest.new(
server_name: server_name,
user_info: {
level: level,
tag: level,
info: {
wkui0_entries_read: 0,
},
},
preferred_max_length: 0xFFFFFFFF,
result_handle: 0
)
response = dcerpc_request(wkst_netr_wksta_enum_user_request)
begin
wkst_netr_wksta_enum_user_response = NetrWkstaUserEnumResponse.read(response)
rescue IOError
raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading WkstNetrWkstaUserEnumResponse'
end
unless wkst_netr_wksta_enum_user_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
raise RubySMB::Dcerpc::Error::WkssvcError,
"Error returned with netr_wksta_enum_user: #{wkst_netr_wksta_enum_user_response.error_status.value} - "\
"#{WindowsError::NTStatus.find_by_retval(wkst_netr_wksta_enum_user_response.error_status.value).join(',')}"
end
wkst_netr_wksta_enum_user_response.user_info.info
end

end
end
end
Expand Down
3 changes: 0 additions & 3 deletions lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ module RubySMB
module Dcerpc
module Wkssvc

# [2.2.2.1 WKSSVC_IDENTIFY_HANDLE](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/9ef94a11-0e5c-49d7-9ac7-68d6f03565de)
class WkssvcIdentifyHandle < Ndr::NdrWideStringPtr; end

# [3.2.4.1 NetrWkstaGetInfo (Opnum 0)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4af41d6f-b800-4de1-af5b-0b15a85f8e04)
class NetrWkstaGetInfoRequest < BinData::Record
attr_reader :opnum
Expand Down
25 changes: 25 additions & 0 deletions lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module RubySMB
module Dcerpc
module Wkssvc

# [3.2.4.3 NetrWkstaUserEnum (Opnum 2)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4af41d6f-b800-4de1-af5b-0b15a85f8e04)
class NetrWkstaUserEnumRequest < BinData::Record
attr_reader :opnum

endian :little

wkssvc_identify_handle :server_name
wksta_user_enum_structure :user_info
ndr_uint32 :preferred_max_length, initial_value: 0xFFFFFFFF
ndr_uint32_ptr :result_handle, initial_value: 0

def initialize_instance
super
@opnum = NETR_WKSTA_USER_ENUM
end
end

end
end
end

25 changes: 25 additions & 0 deletions lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module RubySMB
module Dcerpc
module Wkssvc

# [3.2.4.3 NetrWkstaUserEnum (Opnum 2)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wkst/4af41d6f-b800-4de1-af5b-0b15a85f8e04)
class NetrWkstaUserEnumResponse < BinData::Record
attr_reader :opnum

endian :little

wksta_user_enum_structure :user_info
ndr_uint32_ptr :total_entries
ndr_uint32_ptr :result_handle
ndr_uint32 :error_status

def initialize_instance
super
@opnum = NETR_WKSTA_USER_ENUM
end
end

end
end
end

Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
RSpec.describe RubySMB::Dcerpc::Wkssvc::WkssvcIdentifyHandle do
subject(:packet) { described_class.new }

it 'is a Ndr::NdrWideStringPtr' do
expect(packet).to be_a(RubySMB::Dcerpc::Ndr::NdrWideStringPtr)
end
end

RSpec.describe RubySMB::Dcerpc::Wkssvc::NetrWkstaGetInfoRequest do
subject(:packet) { described_class.new }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@
it 'is little endian' do
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
end
it 'it is a Ndr::NdrStruct' do
it 'is a Ndr::NdrStruct' do
expect(described_class).to be < RubySMB::Dcerpc::Ndr::NdrStruct
end
describe '#level' do
Expand Down
7 changes: 7 additions & 0 deletions spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_identity_handle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
RSpec.describe RubySMB::Dcerpc::Wkssvc::WkssvcIdentifyHandle do
subject(:packet) { described_class.new }

it 'is a Ndr::NdrWideStringPtr' do
expect(packet).to be_a(RubySMB::Dcerpc::Ndr::NdrWideStringPtr)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
RSpec.describe RubySMB::Dcerpc::Wkssvc::NetrWkstaUserEnumRequest do
subject(:packet) { described_class.new }

def random_str(nb = 8)
nb.times.map { rand('a'.ord..'z'.ord).chr }.join
end

it { is_expected.to respond_to :server_name }
it { is_expected.to respond_to :user_info }
it { is_expected.to respond_to :preferred_max_length }
it { is_expected.to respond_to :result_handle }
it { is_expected.to respond_to :opnum }

it 'is little endian' do
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
end
it 'is a BinData::Record' do
expect(packet).to be_a(BinData::Record)
end
describe '#server_name' do
it 'is a WkssvcIdentifyHandle structure' do
expect(packet.server_name).to be_a RubySMB::Dcerpc::Wkssvc::WkssvcIdentifyHandle
end
end
describe '#user_info' do
it 'is a WkstaUserEnumStructure structure' do
expect(packet.user_info).to be_a RubySMB::Dcerpc::Wkssvc::WkstaUserEnumStructure
end
end
describe '#preferred_max_length' do
it 'is a NdrUint32 structure' do
expect(packet.preferred_max_length).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
end

it 'has a default value of 0xFFFFFFFF' do
expect(packet.preferred_max_length).to eq(0xFFFFFFFF)
end
end
describe '#result_handle' do
it 'is a NdrUint32Ptr structure' do
expect(packet.result_handle).to be_a RubySMB::Dcerpc::Ndr::NdrUint32Ptr
end

it 'has a default value of 0' do
expect(packet.result_handle).to eq(0)
end
end
describe '#initialize_instance' do
it 'sets #opnum to NETR_WKSTA_USER_ENUM constant' do
expect(packet.opnum).to eq(RubySMB::Dcerpc::Wkssvc::NETR_WKSTA_USER_ENUM)
end
end
it 'reads itself' do
packet = described_class.new(
server_name: 'TestServer',
user_info: {
level: RubySMB::Dcerpc::Wkssvc::WKSTA_USER_INFO_0,
info: {
wkui0_entries_read: 1,
wkui0_buffer: [{
wkui0_username: random_str
}],
},
},
preferred_max_length: 0xFFFFFFFF,
result_handle: 0
)
binary = packet.to_binary_s
expect(described_class.read(binary)).to eq(packet)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
RSpec.describe RubySMB::Dcerpc::Wkssvc::NetrWkstaUserEnumResponse do
subject(:packet) { described_class.new }

def random_str(nb = 8)
nb.times.map { rand('a'.ord..'z'.ord).chr }.join
end

it { is_expected.to respond_to :user_info }
it { is_expected.to respond_to :total_entries }
it { is_expected.to respond_to :result_handle }
it { is_expected.to respond_to :error_status }

it 'is little endian' do
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
end
it 'is a BinData::Record' do
expect(packet).to be_a(BinData::Record)
end
describe '#user_info' do
it 'is a WkstaUserEnumStructure structure' do
expect(packet.user_info).to be_a RubySMB::Dcerpc::Wkssvc::WkstaUserEnumStructure
end
end
describe '#total_entries' do
it 'is a NdrUint32Ptr structure' do
expect(packet.total_entries).to be_a RubySMB::Dcerpc::Ndr::NdrUint32Ptr
end
end
describe '#result_handle' do
it 'is a NdrUint32Ptr structure' do
expect(packet.result_handle).to be_a RubySMB::Dcerpc::Ndr::NdrUint32Ptr
end
end
describe '#error_status' do
it 'is a NdrUint32 structure' do
expect(packet.error_status).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
end
end
describe '#initialize_instance' do
it 'sets #opnum to NETR_WKSTA_USER_ENUM constant' do
expect(packet.opnum).to eq(RubySMB::Dcerpc::Wkssvc::NETR_WKSTA_USER_ENUM)
end
end
it 'reads itself' do
packet = described_class.new(
user_info: {
level: RubySMB::Dcerpc::Wkssvc::WKSTA_USER_INFO_1,
info: {
wkui1_entries_read: 1,
wkui1_buffer: [{
wkui1_username: random_str,
wkui1_logon_domain: random_str,
wkui1_oth_domains: random_str,
wkui1_logon_server: random_str
}],
},
},
total_entries: 1,
result_handle: 0,
error_status: 0
)
binary = packet.to_binary_s
expect(described_class.read(binary)).to eq(packet)
end
end
Loading

0 comments on commit ce82cb4

Please sign in to comment.