Skip to content

Commit

Permalink
Add ul_type 12 (UPN and DNS info) to pac bindata
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelch-r7 committed Feb 7, 2023
1 parent 8ee6708 commit 782e4c0
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 34 additions & 21 deletions lib/rex/proto/kerberos/credential_cache/krb5_ccache_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'base64'
require 'rex/proto/kerberos/pac/krb5_pac'

module Rex::Proto::Kerberos::CredentialCache
class Krb5CcachePresenter
Expand Down Expand Up @@ -48,8 +49,6 @@ def present(key: nil)
output.join("\n")
end

protected

# @return [Rex::Proto::Kerberos::CredentialCache::Krb5Ccache]
attr_reader :ccache

Expand Down Expand Up @@ -107,11 +106,10 @@ def present_cred(cred, key: nil)
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:'

Expand Down Expand Up @@ -156,50 +154,65 @@ def present_logon_info(info_buffer)
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)
output << "Client ID: #{present_time(client_info.client_id)}".indent(2)
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
Expand Down
144 changes: 144 additions & 0 deletions lib/rex/proto/kerberos/pac/krb5_pac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -381,6 +382,148 @@ def decrypt_serialized_data(key)
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

Expand All @@ -389,6 +532,7 @@ class Krb5PacElement < BinData::Choice
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,45 @@
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
Loading

0 comments on commit 782e4c0

Please sign in to comment.