Add ul_type 12 (UPN and DNS info) to pac bindata
This commit is contained in:
parent
8ee67085c8
commit
782e4c0295
|
@ -150,7 +150,7 @@ GEM
|
|||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
bcrypt (3.1.18)
|
||||
bcrypt_pbkdf (1.1.0)
|
||||
bindata (2.4.14)
|
||||
bindata (2.4.15)
|
||||
bson (4.15.0)
|
||||
builder (3.2.4)
|
||||
byebug (11.1.3)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'base64'
|
||||
require 'rex/proto/kerberos/pac/krb5_pac'
|
||||
|
||||
module Rex::Proto::Kerberos::CredentialCache
|
||||
class Krb5CcachePresenter
|
||||
|
@ -48,8 +49,6 @@ module Rex::Proto::Kerberos::CredentialCache
|
|||
output.join("\n")
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @return [Rex::Proto::Kerberos::CredentialCache::Krb5Ccache]
|
||||
attr_reader :ccache
|
||||
|
||||
|
@ -107,11 +106,10 @@ module Rex::Proto::Kerberos::CredentialCache
|
|||
output.join("\n")
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacInfoBuffer] info_buffer
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5LogonInfo] logon_info
|
||||
# @return [String] A human readable representation of a Logon Information
|
||||
def present_logon_info(info_buffer)
|
||||
validation_info = info_buffer.buffer.pac_element.data
|
||||
|
||||
def present_logon_info(logon_info)
|
||||
validation_info = logon_info.data
|
||||
output = []
|
||||
output << 'Validation Info:'
|
||||
|
||||
|
@ -156,10 +154,9 @@ module Rex::Proto::Kerberos::CredentialCache
|
|||
output.join("\n")
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacInfoBuffer] info_buffer
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5ClientInfo] client_info
|
||||
# @return [String] A human readable representation of a Client Info
|
||||
def present_client_info(info_buffer)
|
||||
client_info = info_buffer.buffer.pac_element
|
||||
def present_client_info(client_info)
|
||||
output = []
|
||||
output << 'Client Info:'
|
||||
output << "Name: '#{client_info.name.encode('utf-8')}'".indent(2)
|
||||
|
@ -167,39 +164,55 @@ module Rex::Proto::Kerberos::CredentialCache
|
|||
output.join("\n")
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacInfoBuffer] info_buffer
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacServerChecksum] server_checksum
|
||||
# @return [String] A human readable representation of a Server Checksum
|
||||
def present_server_checksum(info_buffer)
|
||||
server_checksum = info_buffer.buffer.pac_element
|
||||
|
||||
def present_server_checksum(server_checksum)
|
||||
sig = server_checksum.signature.bytes.map { |x| "#{x.to_s(16).rjust(2, '0')}" }.join
|
||||
"Pac Server Checksum:\n" +
|
||||
"Signature: #{sig}".indent(2)
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacInfoBuffer] info_buffer
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacPrivServerChecksum] priv_server_checksum
|
||||
# @return [String] A human readable representation of a Privilege Server Checksum
|
||||
def present_priv_server_checksum(info_buffer)
|
||||
priv_server_checksum = info_buffer.buffer.pac_element
|
||||
|
||||
def present_priv_server_checksum(priv_server_checksum)
|
||||
sig = priv_server_checksum.signature.bytes.map { |x| "#{x.to_s(16).rjust(2, '0')}" }.join
|
||||
"Pac Privilege Server Checksum:\n" +
|
||||
"Signature: #{sig}".indent(2)
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5UpnDnsInfo] upn_and_dns_info
|
||||
# @return [String] A human readable representation of a UPN and DNS information element
|
||||
def present_upn_and_dns_information(upn_and_dns_info)
|
||||
output = []
|
||||
output << 'UPN and DNS Information:'
|
||||
output << "UPN: #{upn_and_dns_info.upn.encode('utf-8')}".indent(2)
|
||||
output << "DNS Domain Name: #{upn_and_dns_info.dns_domain_name.encode('utf-8')}".indent(2)
|
||||
|
||||
output << "Flags: #{upn_and_dns_info.flags}".indent(2)
|
||||
|
||||
if upn_and_dns_info.has_s_flag?
|
||||
output << "SAM Name: #{upn_and_dns_info.sam_name.encode('utf-8')}".indent(2)
|
||||
output << "SID: #{upn_and_dns_info.sid}".indent(2)
|
||||
end
|
||||
output.join("\n")
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacInfoBuffer] info_buffer
|
||||
# @return [String] A human readable representation of a Pac Info Buffer
|
||||
def present_pac_info_buffer(info_buffer)
|
||||
ul_type = info_buffer.ul_type.to_i
|
||||
pac_element = info_buffer.buffer.pac_element
|
||||
case ul_type
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::LOGON_INFORMATION
|
||||
present_logon_info(info_buffer)
|
||||
present_logon_info(pac_element)
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::CLIENT_INFORMATION
|
||||
present_client_info(info_buffer)
|
||||
present_client_info(pac_element)
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::SERVER_CHECKSUM
|
||||
present_server_checksum(info_buffer)
|
||||
present_server_checksum(pac_element)
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM
|
||||
present_priv_server_checksum(info_buffer)
|
||||
present_priv_server_checksum(pac_element)
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::USER_PRINCIPAL_NAME_AND_DNS_INFORMATION
|
||||
present_upn_and_dns_information(pac_element)
|
||||
else
|
||||
ul_type_name = Rex::Proto::Kerberos::Pac::Krb5PacElementType.const_name(ul_type)
|
||||
ul_type_name = ul_type_name.gsub('_', ' ').capitalize if ul_type_name
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'bindata'
|
||||
require 'ruby_smb/dcerpc'
|
||||
require 'rex/proto/ms_dtyp'
|
||||
|
||||
# full MIDL spec for PAC
|
||||
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/1d4912dd-5115-4124-94b6-fa414add575f
|
||||
|
@ -381,6 +382,148 @@ module Rex::Proto::Kerberos::Pac
|
|||
end
|
||||
end
|
||||
|
||||
# See [2.10 UPN_DNS_INFO](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/1c0d6e11-6443-4846-b744-f9f810a504eb)
|
||||
class Krb5UpnDnsInfo < BinData::Record
|
||||
auto_call_delayed_io
|
||||
endian :little
|
||||
# @!attribute [r] ul_type
|
||||
# @return [Integer] Describes the type of data present in the buffer
|
||||
virtual :ul_type, value: Krb5PacElementType::USER_PRINCIPAL_NAME_AND_DNS_INFORMATION
|
||||
|
||||
# @!attribute [rw] upn_length
|
||||
# @return [Integer] The length of the UPN
|
||||
uint16 :upn_length, value: -> { upn.num_bytes }
|
||||
|
||||
# @!attribute [rw] upn_offset
|
||||
# @return [Integer] The relative offset of the UPN from the beginning of this structure
|
||||
uint16 :upn_offset
|
||||
|
||||
# @!attribute [rw] dns_domain_name_length
|
||||
# @return [Integer] The length of the DNS domain name
|
||||
uint16 :dns_domain_name_length, value: -> { dns_domain_name.num_bytes }
|
||||
|
||||
# @!attribute [rw] dns_domain_name_offset
|
||||
# @return [Integer] The relative offset of the DNS domain name from the beginning of this structure
|
||||
uint16 :dns_domain_name_offset
|
||||
|
||||
# @!attribute [rw] flags
|
||||
# @return [Integer]
|
||||
# U flag (bit 0) The user account object does not have the userPrincipalName attribute.
|
||||
# S flag (bit 1) The structure has been extended with the user account’s SAM Name and SID.
|
||||
# The remaining bits are ignored.
|
||||
uint32 :flags
|
||||
|
||||
# @!attribute [rw] sam_name_length
|
||||
# @return [Integer] The length of the SAM name
|
||||
# Only available if the S flag is set
|
||||
uint16 :sam_name_length, value: -> { sam_name.num_bytes }, onlyif: :has_s_flag?
|
||||
|
||||
# @!attribute [rw] sam_name_offset
|
||||
# @return [Integer] The relative offset of the SAM name from the beginning of this structure
|
||||
# Only available if the S flag is set
|
||||
uint16 :sam_name_offset, onlyif: :has_s_flag?
|
||||
|
||||
# @!attribute [rw] sid_length
|
||||
# @return [Integer] The length of the SID
|
||||
# Only available if the S flag is set
|
||||
uint16 :sid_length, value: -> { sid.num_bytes }, onlyif: :has_s_flag?
|
||||
|
||||
# @!attribute [rw] sid_offset
|
||||
# @return [Integer] The relative offset of the SID from the beginning of this structure
|
||||
# Only available if the S flag is set
|
||||
uint16 :sid_offset, onlyif: :has_s_flag?
|
||||
|
||||
# @!attribute [rw] upn
|
||||
# @return [String] The UPN (User Principal Name) (e.g. test@windomain.local)
|
||||
delayed_io :upn, read_abs_offset: -> { self.abs_offset + upn_offset } do
|
||||
string16 read_length: :upn_length
|
||||
end
|
||||
|
||||
# @!attribute [rw] dns_domain_name
|
||||
# @return [String] The DNS Domain Name (e.g. WINDOMAIN.LOCAL)
|
||||
delayed_io :dns_domain_name, read_abs_offset: -> { self.abs_offset + dns_domain_name_offset } do
|
||||
string16 read_length: :dns_domain_name_length
|
||||
end
|
||||
|
||||
# @!attribute [rw] sam_name
|
||||
# @return [String] The SAM Name (e.g. test)
|
||||
delayed_io :sam_name, read_abs_offset: -> { self.abs_offset + sam_name_offset }, onlyif: -> { has_s_flag? } do
|
||||
string16 read_length: :sam_name_length
|
||||
end
|
||||
|
||||
# @!attribute [rw] sid
|
||||
# @return [MsDtypSid] The SID (e.g. S-1-5-32-544)
|
||||
delayed_io :sid, read_abs_offset: -> { self.abs_offset + sid_offset }, onlyif: -> { has_s_flag? } do
|
||||
ms_dtyp_sid
|
||||
end
|
||||
|
||||
# def initialize_instance(*args)
|
||||
# super
|
||||
# set_offsets!
|
||||
# end
|
||||
# @return [Boolean] Returns the value of the S flag
|
||||
def has_s_flag?
|
||||
flags.anybits?(0b10)
|
||||
end
|
||||
|
||||
# @param [Boolean] bool The value to set the S flag to
|
||||
# @return [void]
|
||||
def set_s_flag(bool)
|
||||
set_flag_bit(1, bool)
|
||||
end
|
||||
|
||||
# @return [Boolean] Returns the value of the U flag
|
||||
def has_u_flag?
|
||||
flags.anybits?(0b01)
|
||||
end
|
||||
|
||||
# @param [Boolean] bool The value to set the U flag to
|
||||
# @return [void]
|
||||
def set_u_flag(bool)
|
||||
set_flag_bit(0, bool)
|
||||
end
|
||||
|
||||
# @param [Integer] upn The relative offset for the upn
|
||||
# @param [Integer] dns_domain_name The relative offset for the dns_domain_name
|
||||
# @param [Integer] sam_name The relative offset for the sam_name
|
||||
# @param [Integer] sid The relative offset for the sid
|
||||
# @return [void]
|
||||
#
|
||||
# Allows you to specify the offsets for the contents, otherwise defaults them
|
||||
def set_offsets!(upn: nil, dns_domain_name: nil, sam_name: nil, sid: nil)
|
||||
self.upn_offset = upn || calc_upn_offset
|
||||
self.dns_domain_name_offset = dns_domain_name || calc_dns_domain_name_offset
|
||||
self.sam_name_offset = sam_name || calc_sam_name_offset
|
||||
self.sid_offset = sid || calc_sid_offset
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_flag_bit(position, bool)
|
||||
if bool
|
||||
self.flags |= (1 << position)
|
||||
else
|
||||
self.flags &= ~(1 << position)
|
||||
end
|
||||
end
|
||||
|
||||
def calc_upn_offset
|
||||
has_s_flag? ? 24 : 16
|
||||
end
|
||||
|
||||
def calc_dns_domain_name_offset
|
||||
upn_offset + upn_length
|
||||
end
|
||||
|
||||
def calc_sam_name_offset
|
||||
dns_domain_name_offset + dns_domain_name_length
|
||||
end
|
||||
|
||||
def calc_sid_offset
|
||||
sam_name_offset + sam_name_length
|
||||
end
|
||||
end
|
||||
|
||||
class Krb5PacElement < BinData::Choice
|
||||
mandatory_parameter :data_length
|
||||
|
||||
|
@ -389,6 +532,7 @@ module Rex::Proto::Kerberos::Pac
|
|||
krb5_pac_server_checksum Krb5PacElementType::SERVER_CHECKSUM
|
||||
krb5_pac_priv_server_checksum Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM
|
||||
krb5_pac_credential_info Krb5PacElementType::CREDENTIAL_INFORMATION, data_length: :data_length
|
||||
krb5_upn_dns_info Krb5PacElementType::USER_PRINCIPAL_NAME_AND_DNS_INFORMATION
|
||||
unknown_pac_element :default, data_length: :data_length, selection: :selection
|
||||
end
|
||||
|
||||
|
|
|
@ -253,4 +253,45 @@ RSpec.describe Rex::Proto::Kerberos::CredentialCache::Krb5CcachePresenter do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#present_upn_and_dns_information' do
|
||||
let(:upn) { 'test@windomain.local' }
|
||||
let(:dns_domain_name) { 'WINDOMAIN.LOCAL' }
|
||||
let(:sam_name) { 'test' }
|
||||
let(:sid) { 'S-1-5-32-544' }
|
||||
|
||||
context 'with no sam name or sid' do
|
||||
let(:flags) { 0b01 }
|
||||
let(:upn_and_dns_info) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5UpnDnsInfo.new(upn: upn, dns_domain_name: dns_domain_name, flags: flags)
|
||||
end
|
||||
it 'returns the correct string' do
|
||||
expect(subject.present_upn_and_dns_information(upn_and_dns_info)).to eq <<~EOF.rstrip
|
||||
UPN and DNS Information:
|
||||
UPN: test@windomain.local
|
||||
DNS Domain Name: WINDOMAIN.LOCAL
|
||||
Flags: 1
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sam name and sid' do
|
||||
let(:flags) { 0b11 }
|
||||
let(:upn_and_dns_info) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5UpnDnsInfo.new(
|
||||
upn: upn, dns_domain_name: dns_domain_name, sam_name: sam_name, sid: sid, flags: flags
|
||||
)
|
||||
end
|
||||
it 'returns the correct string' do
|
||||
expect(subject.present_upn_and_dns_information(upn_and_dns_info)).to eq <<~EOF.rstrip
|
||||
UPN and DNS Information:
|
||||
UPN: test@windomain.local
|
||||
DNS Domain Name: WINDOMAIN.LOCAL
|
||||
Flags: 3
|
||||
SAM Name: test
|
||||
SID: S-1-5-32-544
|
||||
EOF
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -300,3 +300,133 @@ RSpec.describe Rex::Proto::Kerberos::Pac::Krb5PacCredentialInfo do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Rex::Proto::Kerberos::Pac::Krb5UpnDnsInfo do
|
||||
let(:upn) { 'test@windomain.local' }
|
||||
let(:dns_domain_name) { 'WINDOMAIN.LOCAL' }
|
||||
let(:sam_name) { 'test' }
|
||||
let(:sid) { 'S-1-5-32-544' }
|
||||
|
||||
let(:sample) do
|
||||
"\x28\x00\x10\x00\x1e\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x74\x00\x65\x00\x73\x00\x74\x00\x40\x00\x77\x00\x69\x00\x6e\x00" \
|
||||
"\x64\x00\x6f\x00\x6d\x00\x61\x00\x69\x00\x6e\x00\x2e\x00\x6c\x00" \
|
||||
"\x6f\x00\x63\x00\x61\x00\x6c\x00\x57\x00\x49\x00\x4e\x00\x44\x00" \
|
||||
"\x4f\x00\x4d\x00\x41\x00\x49\x00\x4e\x00\x2e\x00\x4c\x00\x4f\x00" \
|
||||
"\x43\x00\x41\x00\x4c\x00"
|
||||
end
|
||||
|
||||
let(:sample_ext) do
|
||||
"\x28\x00\x18\x00\x1e\x00\x40\x00\x02\x00\x00\x00\x08\x00\x5e\x00" \
|
||||
"\x10\x00\x66\x00\x00\x00\x00\x00\x74\x00\x65\x00\x73\x00\x74\x00" \
|
||||
"\x40\x00\x77\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x6d\x00\x61\x00" \
|
||||
"\x69\x00\x6e\x00\x2e\x00\x6c\x00\x6f\x00\x63\x00\x61\x00\x6c\x00" \
|
||||
"\x57\x00\x49\x00\x4e\x00\x44\x00\x4f\x00\x4d\x00\x41\x00\x49\x00" \
|
||||
"\x4e\x00\x2e\x00\x4c\x00\x4f\x00\x43\x00\x41\x00\x4c\x00\x74\x00" \
|
||||
"\x65\x00\x73\x00\x74\x00\x01\x02\x00\x00\x00\x00\x00\x05\x20\x00" \
|
||||
"\x00\x00\x20\x02\x00\x00"
|
||||
end
|
||||
|
||||
context 'with non-extended upn dns info' do
|
||||
describe '#read' do
|
||||
it 'correctly parses the binary data' do
|
||||
upn_dns_info = described_class.read(sample)
|
||||
expect(upn_dns_info.to_binary_s).to eq(sample)
|
||||
end
|
||||
|
||||
it 'creates a valid upn dns info structure' do
|
||||
upn_dns_info = described_class.new(
|
||||
upn: upn,
|
||||
dns_domain_name: dns_domain_name,
|
||||
flags: 1
|
||||
)
|
||||
|
||||
upn_dns_info.set_offsets!
|
||||
|
||||
parsed_sample = described_class.read(sample)
|
||||
expect(upn_dns_info).to eq(parsed_sample)
|
||||
end
|
||||
|
||||
it 'ignores sam/sid values if set' do
|
||||
upn_dns_info = described_class.new(
|
||||
upn: upn,
|
||||
dns_domain_name: dns_domain_name,
|
||||
sam_name: sam_name,
|
||||
sid: sid,
|
||||
flags: 1
|
||||
)
|
||||
upn_dns_info.set_offsets!
|
||||
|
||||
parsed_sample = described_class.read(sample)
|
||||
expect(upn_dns_info).to eq(parsed_sample)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#write' do
|
||||
it 'outputs the expected binary representation' do
|
||||
upn_dns_info = described_class.new(
|
||||
upn: upn,
|
||||
dns_domain_name: dns_domain_name,
|
||||
flags: 1
|
||||
)
|
||||
upn_dns_info.set_offsets!
|
||||
|
||||
binary = upn_dns_info.to_binary_s
|
||||
expect(binary).to eq(sample)
|
||||
end
|
||||
|
||||
it 'ignores sam/sid values if set' do
|
||||
upn_dns_info = described_class.new(
|
||||
upn: upn,
|
||||
dns_domain_name: dns_domain_name,
|
||||
sam_name: sam_name,
|
||||
sid: sid,
|
||||
flags: 1
|
||||
)
|
||||
upn_dns_info.set_offsets!
|
||||
|
||||
binary = upn_dns_info.to_binary_s
|
||||
expect(binary).to eq(sample)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with extended upn dns info' do
|
||||
describe '#read' do
|
||||
it 'correctly parses the binary data' do
|
||||
upn_dns_info = described_class.read(sample_ext)
|
||||
expect(upn_dns_info.to_binary_s).to eq(sample_ext)
|
||||
end
|
||||
|
||||
it 'creates a valid upn dns info structure' do
|
||||
upn_dns_info = described_class.new(
|
||||
upn: upn,
|
||||
dns_domain_name: dns_domain_name,
|
||||
sam_name: sam_name,
|
||||
sid: sid,
|
||||
flags: 2
|
||||
)
|
||||
upn_dns_info.set_offsets!
|
||||
|
||||
parsed_sample = described_class.read(sample_ext)
|
||||
expect(upn_dns_info).to eq(parsed_sample)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#write' do
|
||||
it 'outputs the expected binary representation' do
|
||||
upn_dns_info = described_class.new(
|
||||
upn: upn,
|
||||
dns_domain_name: dns_domain_name,
|
||||
sam_name: sam_name,
|
||||
sid: sid,
|
||||
flags: 2
|
||||
)
|
||||
upn_dns_info.set_offsets!
|
||||
|
||||
binary = upn_dns_info.to_binary_s
|
||||
expect(binary).to eq(sample_ext)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue