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

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