Module: Msf::Exploit::Remote::MsGkdi
- Defined in:
- lib/msf/core/exploit/remote/ms_gkdi.rb
Defined Under Namespace
Classes: GkdiGroupKeyIdentifier
Constant Summary collapse
- KDS_SERVICE_LABEL =
"KDS service\0".encode('UTF-16LE').force_encoding('ASCII-8BIT')
- KDS_PUBLIC_KEY_LABEL =
"KDS public key\0".encode('UTF-16LE').force_encoding('ASCII-8BIT')
Instance Method Summary collapse
- #bind_gkdi(dcerpc_client) ⇒ Object
- #connect_gkdi(opts = {}) ⇒ Object
- #gkdi_compute_kek(gke, key_identifier) ⇒ Object
- #gkdi_compute_kek_pkey(gke, key_identifier, l2_key) ⇒ Object
- #gkdi_compute_l2_key(gke, key_identifier) ⇒ Object
- #gkdi_get_endpoints(opts = {}) ⇒ Object
- #gkdi_get_kek(opts = {}) ⇒ Object
-
#gkdi_kdf_counter(length, key_material, other_info) ⇒ Object
this is mostly a variation on NIST SP 800-108.
Instance Method Details
#bind_gkdi(dcerpc_client) ⇒ Object
67 68 69 70 71 72 73 74 75 76 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 67 def bind_gkdi(dcerpc_client) tower = gkdi_get_endpoints.first dcerpc_client.connect(port: tower[:port]) vprint_status("Binding to GKDI via #{tower[:endpoint]}...") dcerpc_client.bind( auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY, auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT ) vprint_status('Bound to GKDI') end |
#connect_gkdi(opts = {}) ⇒ Object
54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 54 def connect_gkdi(opts = {}) vprint_status('Connecting to Group Key Distribution (GKDI) Protocol') dcerpc_client = RubySMB::Dcerpc::Client.new( opts.fetch(:rhost) { rhost }, RubySMB::Dcerpc::Gkdi, username: opts.fetch(:username) { datastore['USERNAME'] }, password: opts.fetch(:password) { datastore['PASSWORD'] } ) bind_gkdi(dcerpc_client) dcerpc_client end |
#gkdi_compute_kek(gke, key_identifier) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 94 def gkdi_compute_kek(gke, key_identifier) l2_key = gkdi_compute_l2_key(gke, key_identifier) if (key_identifier.dw_flags & 1) == 0 raise NotImplementedError.new("only public-private key pairs are supported") end secret = gkdi_compute_kek_pkey(gke, key_identifier, l2_key) Rex::Crypto::KeyDerivation::NIST_SP_800_108.counter_hmac( secret, 32, gke.kdf_parameters.hash_algorithm_name.encode, label: KDS_SERVICE_LABEL, context: KDS_PUBLIC_KEY_LABEL ).first end |
#gkdi_compute_kek_pkey(gke, key_identifier, l2_key) ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 111 def gkdi_compute_kek_pkey(gke, key_identifier, l2_key) private_key = Rex::Crypto::KeyDerivation::NIST_SP_800_108.counter_hmac( l2_key, (gke.private_key_length / 8.0).ceil, gke.kdf_parameters.hash_algorithm_name.encode, context: gke.secret_agreement_algorithm.to_binary_s, label: KDS_SERVICE_LABEL ).first unless (algorithm = gke.secret_agreement_algorithm.encode) == 'DH' raise NotImplementedError.new("unsupported secret agreement algorithm: #{algorithm}") end ffc_dh_key = RubySMB::Dcerpc::Gkdi::GkdiFfcDhKey.read(key_identifier.context.pack('C*')) base = Rex::Crypto.bytes_to_int(ffc_dh_key.public_key.pack('C*')) exp = Rex::Crypto.bytes_to_int(private_key) mod = Rex::Crypto.bytes_to_int(ffc_dh_key.field_order.pack('C*')) key_material = Rex::Crypto.int_to_bytes(base.pow(exp, mod)) gkdi_kdf_counter(32, key_material, "SHA512\0".encode('UTF-16LE').force_encoding('ASCII-8BIT') + KDS_PUBLIC_KEY_LABEL + KDS_SERVICE_LABEL) end |
#gkdi_compute_l2_key(gke, key_identifier) ⇒ Object
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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 133 def gkdi_compute_l2_key(gke, key_identifier) unless (algorithm = gke.kdf_algorithm.encode) == 'SP800_108_CTR_HMAC' # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/5d373568-dd68-499b-bd06-a3ce16ca7117 raise NotImplementedError.new("unsupported key derivation function algorithm: #{algorithm}") end l1 = gke.l1_index.to_i l1_key = gke.l1_key.pack('C*') l2 = gke.l2_index.to_i l2_key = gke.l2_key.pack('C*') reseed_l2 = (l2 == 31 || l1 != key_identifier.l1_index) l1 -= 1 if l2 != 31 && l1 != key_identifier.l1_index while l1 != key_identifier.l1_index reseed_l2 = true l1 -= 1 l1_key = Rex::Crypto::KeyDerivation::NIST_SP_800_108.counter_hmac( l1_key, 64, gke.kdf_parameters.hash_algorithm_name.encode, context: gke.root_key_identifier.to_binary_s + [ gke.l0_index, l1, -1 ].pack('l<l<l<'), label: KDS_SERVICE_LABEL ).first end if reseed_l2 l2 = 31 l2_key = Rex::Crypto::KeyDerivation::NIST_SP_800_108.counter_hmac( l1_key, 64, gke.kdf_parameters.hash_algorithm_name.encode, context: gke.root_key_identifier.to_binary_s + [ gke.l0_index, l1, l2 ].pack('l<l<l<'), label: KDS_SERVICE_LABEL ).first end while l2 != key_identifier.l2_index l2 -= 1 l2_key = Rex::Crypto::KeyDerivation::NIST_SP_800_108.counter_hmac( l2_key, 64, gke.kdf_parameters.hash_algorithm_name.encode, context: gke.root_key_identifier.to_binary_s + [ gke.l0_index, l1, l2 ].pack('l<l<l<'), label: KDS_SERVICE_LABEL ).first end l2_key end |
#gkdi_get_endpoints(opts = {}) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 78 def gkdi_get_endpoints(opts = {}) vprint_status('Mapping GKDI endpoints...') dcerpc_client = RubySMB::Dcerpc::Client.new( opts.fetch(:rhost) { rhost }, RubySMB::Dcerpc::Epm ) dcerpc_client.connect dcerpc_client.bind # This works around an odd error where if the target has just booted, then no towers (endpoint connection infos) # will be returned if max_towers is set to 1. Here we map it our self and set max_towers to a higher number to work # around the behavior. Subsequent mapping attempts will work with max_towers set to 1, but 4 will always work. towers = dcerpc_client.ept_map_endpoint(RubySMB::Dcerpc::Gkdi, max_towers: 4) dcerpc_client.close towers end |
#gkdi_get_kek(opts = {}) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 39 def gkdi_get_kek(opts = {}) gkdi = opts.fetch(:client) { connect_gkdi(opts) } key_identifier = opts.fetch(:key_identifier) gke = gkdi.gkdi_get_key( opts.fetch(:security_descriptor), key_identifier[:root_key_identifier].to_s, key_identifier[:l0_index], key_identifier[:l1_index], key_identifier[:l2_index] ) gkdi_compute_kek(gke, key_identifier) end |
#gkdi_kdf_counter(length, key_material, other_info) ⇒ Object
this is mostly a variation on NIST SP 800-108
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/msf/core/exploit/remote/ms_gkdi.rb', line 189 def gkdi_kdf_counter(length, key_material, other_info) prf = -> (data) { OpenSSL::Digest.new('SHA256', data).digest } key_block = '' counter = 0 while key_block.length < length counter += 1 raise RangeError.new('counter overflow') if counter > 0xffffffff info = [ counter ].pack('L>') + key_material + other_info key_block << prf.call(info) end key_block[...length] end |