Add ul_type 12 (UPN and DNS info) to pac bindata

This commit is contained in:
dwelch-r7 2023-02-06 16:46:49 +00:00 committed by Dean Welch
parent 8ee67085c8
commit 782e4c0295
5 changed files with 350 additions and 22 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 accounts 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

View File

@ -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

View File

@ -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