// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024 Eduardo Aguiar
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "registry_data_impl_msdcc.h"
#include <mobius/decoder/data_decoder.h>
#include <mobius/crypt/cipher.h>
#include <mobius/crypt/hmac.h>

namespace mobius::os::win::registry
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief decrypt data using CTS AES-128 with CBC
//! \param key AES key
//! \param iv Initialization Vector
//! \param ciphertext ciphertext
//! \return plaintext
//! \see https://en.wikipedia.org/wiki/Ciphertext_stealing
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::bytearray decrypt_cts_aes_128 (
  const mobius::bytearray& key,
  const mobius::bytearray& iv,
  const mobius::bytearray& ciphertext)
{
  mobius::bytearray plaintext;
  mobius::bytearray l_ciphertext = ciphertext;
  constexpr mobius::bytearray::size_type block_size = 16;

  // if ciphertext data has at least two blocks...
  if (ciphertext.size () >= block_size * 2)
    {
      std::uint32_t padsize = block_size - (ciphertext.size () % block_size);

      // decrypt Cn-1 (second to last block)
      if (padsize < block_size)
        {
          auto aes1 = mobius::crypt::new_cipher_ecb ("aes", key);
          mobius::bytearray::size_type pos = ciphertext.size () - block_size * 2 + padsize;
          mobius::bytearray dn = aes1.decrypt (ciphertext.slice (pos, pos + block_size - 1));

          // pad ciphertext with DN
          l_ciphertext += dn.slice (dn.size () - padsize, dn.size () - 1);
        }

      // swap last two blocks
      mobius::bytearray::size_type pos = l_ciphertext.size () - block_size * 2;
      l_ciphertext = l_ciphertext.slice (0, pos - 1) + l_ciphertext.slice (pos + block_size, pos + block_size * 2 - 1) + l_ciphertext.slice (pos, pos + block_size - 1);
    }

  // decrypt data
  auto c = mobius::crypt::new_cipher_cbc ("aes", key, iv);
  plaintext = c.decrypt (l_ciphertext);
  plaintext.resize (ciphertext.size ());

  return plaintext;
}

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief constructor
//! \param nlkm NL$KM decrypted key
//! \param encrypted_data value's encrypted data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
registry_data_impl_msdcc::registry_data_impl_msdcc (
  const mobius::bytearray& nlkm,
  const mobius::bytearray& encrypted_data)
  : nlkm_ (nlkm),
    encrypted_data_ (encrypted_data)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief load data on demand
//! \see http://moyix.blogspot.com.br/2008/02/cached-domain-credentials.html
//! \see https://github.com/Neohapsis/creddump7/blob/master/framework/win32/domcachedump.py
//! \see https://github.com/gentilkiwi/mimikatz
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
registry_data_impl_msdcc::_load_data () const
{
  // return if data is loaded
  if (data_loaded_)
    return;

  // decode encrypted data
  mobius::decoder::data_decoder decoder (encrypted_data_);
  decoder.skip (48);
  std::uint16_t encrypted = decoder.get_uint16_le ();
  std::uint16_t algorithm = decoder.get_uint16_le ();
  decoder.skip (12);
  mobius::bytearray iv = decoder.get_bytearray_by_size (16);
  decoder.skip (16);
  mobius::bytearray data = decoder.get_bytearray_by_size (encrypted_data_.size () - 96);

  // decrypt data, if necessary
  if (encrypted)
    {
      // MSDCC1 - MS domain Cached Credentials v1
      if (algorithm == 0)
        {
          mobius::crypt::hmac hmac ("md5", nlkm_);
          hmac.update (iv);
          auto rc4_key = hmac.get_digest ();

          auto rc4 = mobius::crypt::new_cipher_stream ("rc4", rc4_key);
          data_ = encrypted_data_.slice (0, 95) + rc4.decrypt (data);
        }

      // MSDCC2 - MS domain Cached Credentials v2
      else if (algorithm == 10)
        {
          auto key = nlkm_.slice (0, 15);
          data_ = encrypted_data_.slice (0, 95) + decrypt_cts_aes_128 (key, iv, data);
        }

      // crop data
      if (data_.size () >= 0x8b)
        {
          mobius::decoder::data_decoder decoder (data_);
          decoder.skip (0x88);
          std::uint32_t size = decoder.get_uint32_le ();
          data_.resize (size);
        }
    }

  else
    data_ = encrypted_data_;

  // set subkeys loaded
  data_loaded_ = true;
}

} // namespace mobius::os::win::registry
