# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 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/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import traceback
import mobius
import pymobius.app.chromium

DPAPI_GUID = b'\xd0\x8c\x9d\xdf\x01\x15\xd1\x11\x8c\x7a\x00\xc0\x4f\xc2\x97\xeb'

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Generic dataholder class
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class dataholder (object):
  pass

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Retrieves and decrypts Chromium Passwords
# @author Eduardo Aguiar
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Ant (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, item):
    self.name = 'Turing Chromium Passwords'
    self.version = '1.0'
    self.__item = item

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Run ant
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def run (self):
    self.__secrets = []
    self.__urls = set ()
    self.__count = 0

    try:
      self.__retrieve_chromium_passwords ()
    except Exception as e:
      mobius.core.logf ('WRN %s %s' % (str (e), traceback.format_exc ()))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Event <i>on_export_data</i>: Export data to mediator ant
  # @param ant Mediator ant object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def on_export_data (self, ant):
    pass

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Event <i>on_stop</i>: Terminate this ant
  # @param ant Mediator ant object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def on_stop (self, ant):

    # add URLs to Turing, as IE entropy hashes
    turing = mobius.turing.turing ()
    transaction = turing.new_transaction ()

    for url in self.__urls:
      h = mobius.encoder.hexstring (mobius.os.win.hash_ie_entropy (url))
      turing.set_hash ('ie.entropy', h, url)

    transaction.commit ()

    # write status
    mobius.core.logf ('INF %d/%d Chromium password(s) decrypted' % (self.__count, len (self.__secrets)))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Event <i>on_key</i>: Process key object
  # @param ant Mediator ant object
  # @param key Key object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def on_key (self, ant, key):
    if key.type != 'dpapi.user':
      return

    for secret in self.__secrets:
      blob = secret.blob

      if not blob.is_decrypted () and blob.master_key_guid == key.id:
        self.__decrypt_secret (ant, secret, key)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Try to decrypt secret
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __decrypt_secret (self, ant, secret, key):
    blob = secret.blob

    if blob.decrypt (key.value):
      password = blob.plain_text.rstrip (b'\0')

      if password and password[-1] > 16:
        p = dataholder ()
        p.type = 'net.http'
        p.value = password.decode ('utf-8')
        p.description = "Web password. URL: " + secret.p.url

        p.metadata = []
        p.metadata.append (('URL', secret.p.url))
        p.metadata.append (('User ID', secret.p.username))
        p.metadata.append (('Profile path', secret.profile.path))
        p.metadata.append (('Application', secret.profile.app_name))

        self.__count += 1
        ant.add_password (p)

    else:
      mobius.core.logf ('WRN Chromium DPAPI blob not decrypted')

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve Chromium browsers passwords
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_chromium_passwords (self):
    model = pymobius.app.chromium.model (self.__item)

    for profile in model.get_profiles ():
      try:
        self.__retrieve_chromium_passwords_from_profile (profile)
      except Exception as e:
        mobius.core.logf ('WRN %s %s' % (str (e), traceback.format_exc ()))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve Chromium passwords from profile
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_chromium_passwords_from_profile (self, profile):
    urls = set ()

    # get passwords from profile
    for p in profile.get_stored_passwords ():
      self.__urls.add (p.url)

      if p.has_password and DPAPI_GUID in p.password:
        secret = dataholder ()
        secret.blob = mobius.os.win.dpapi.blob (p.password)
        secret.profile = profile
        secret.p = p

        self.__secrets.append (secret)
