# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# 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 mobius
import pymobius.ant.turing.add_passwords_to_database
import pymobius.ant.turing.chromium_passwords
import pymobius.ant.turing.credentials
import pymobius.ant.turing.credhist_hashes
import pymobius.ant.turing.custom_dic
import pymobius.ant.turing.dpapi_sys_mk
import pymobius.ant.turing.dpapi_user_mk
import pymobius.ant.turing.ie_passwords
import pymobius.ant.turing.registry_hashes
import pymobius.ant.turing.registry_passwords
import pymobius.ant.turing.wifi_passwords
import pymobius.ant.turing.test_case_passwords
import pymobius.ant.turing.test_hash_passwords
import pymobius.vfs

import re
import traceback

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Constants
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
ANT_ID = 'turing'
ANT_NAME = 'Turing'
ANT_VERSION = '1.0'

REGEX_WORDS = re.compile ("(\w[\w']*\w|\w)")

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

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Ant: Turing
# @author Eduardo Aguiar
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Ant (object):

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

    self.__item = item
    self.__passwords = []
    self.__hashes = []
    self.__keys = []

    self.__unique_passwords = set ()

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Run ant
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def run (self):
    vfs = pymobius.vfs.get_item_vfs (self.__item)

    if not vfs:
      raise Exception ('Datasource is not available')

    mobius.core.logf ('INF ant.turing started')

    # create ants
    self.__ants = [
      pymobius.ant.turing.chromium_passwords.Ant (self.__item),
      pymobius.ant.turing.credentials.Ant (self.__item),
      pymobius.ant.turing.credhist_hashes.Ant (self.__item),
      pymobius.ant.turing.custom_dic.Ant (self.__item),
      pymobius.ant.turing.dpapi_sys_mk.Ant (self.__item),
      pymobius.ant.turing.dpapi_user_mk.Ant (self.__item),
      pymobius.ant.turing.ie_passwords.Ant (self.__item),
      pymobius.ant.turing.registry_hashes.Ant (self.__item),
      pymobius.ant.turing.registry_passwords.Ant (self.__item),
      pymobius.ant.turing.wifi_passwords.Ant (self.__item),
      pymobius.ant.turing.test_hash_passwords.Ant (self.__item),
      pymobius.ant.turing.test_case_passwords.Ant (self.__item),
      pymobius.ant.turing.add_passwords_to_database.Ant (self.__item)
    ]

    # run sub-ants
    for ant in self.__ants:
      try:
        mobius.core.logf ('DBG ant.run started: %s' % ant.name)
        ant.run ()
        mobius.core.logf ('DBG ant.run ended: %s' % ant.name)
      except Exception as e:
        mobius.core.logf ('WRN (%s.run) %s %s' % (ant.name, str (e), traceback.format_exc ()))

    # consolidate data
    for ant in self.__ants:
      try:
        mobius.core.logf ('DBG ant.on_export_data started: %s' % ant.name)
        ant.on_export_data (self)
        mobius.core.logf ('DBG ant.on_export_data ended: %s' % ant.name)
      except Exception as e:
        mobius.core.logf ('WRN (%s.on_export_data) %s %s' % (ant.name, str (e), traceback.format_exc ()))

    # terminate sub-ants
    for ant in self.__ants:
      try:
        mobius.core.logf ('DBG ant.on_stop started: %s' % ant.name)
        ant.on_stop (self)
        mobius.core.logf ('DBG ant.on_stop ended: %s' % ant.name)
      except Exception as e:
        mobius.core.logf ('WRN (%s.on_stop) %s %s' % (ant.name, str (e), traceback.format_exc ()))

    # log info
    mobius.core.logf ('INF passwords = %d' % len (self.__passwords))
    mobius.core.logf ('INF hashes = %d' % len (self.__hashes))
    mobius.core.logf ('INF keys = %d' % len (self.__keys))

    # save data
    self.__save_data ()

    mobius.core.logf ('INF finished')

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Save data into model
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __save_data (self):
    c = self.__item.case
    transaction = c.new_transaction ()

    # remove old data
    self.__item.remove_passwords ()
    self.__item.remove_password_hashes ()
    self.__item.remove_encryption_keys ()

    # save passwords
    passwords = [(p.type, p.description, p.value, p) for p in self.__passwords]

    for ptype, description, value, pobj in sorted (passwords, key=lambda x: x[:3]):
      p = self.__item.new_password (ptype, value, description)

      metadata = mobius.pod.map ()

      for name, value in pobj.metadata:
        metadata.set (name, value)

      p.metadata = metadata

    # save password hashes
    hashes = [(h.type, h.description, h.value, h) for h in self.__hashes]

    for htype, description, value, hobj in sorted (hashes):
      hash_value = mobius.encoder.hexstring (value)

      h = self.__item.new_password_hash (htype, hash_value, description)

      if hobj.password != None:
        h.password = hobj.password

      metadata = mobius.pod.map ()

      for name, value in hobj.metadata:
        metadata.set (name, value)

      h.metadata = metadata

    # save encryption keys
    keys = [(k.type, k.id, k.value, k) for k in self.__keys]

    for k_type, k_id, k_value, k in sorted (keys):
      key = self.__item.new_encryption_key (k_type, k_id, k_value)

      metadata = mobius.pod.map ()

      for name, value in k.metadata:
        metadata.set (name, value)

      key.metadata = metadata

    # set ant run
    self.__item.set_ant (ANT_ID, ANT_NAME, ANT_VERSION)
    transaction.commit ()

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Add key to model
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def add_key (self, key):
    self.__keys.append (key)

    for ant in self.__ants:
      try:
        f = getattr (ant, 'on_key', None)

        if f:
          mobius.core.logf ('DBG ant.on_key started: %s' % ant.name)
          f (self, key)
          mobius.core.logf ('DBG ant.on_key ended: %s' % ant.name)
      except Exception as e:
        mobius.core.logf ('WRN (%s.on_key) %s %s' % (ant.name, str (e), traceback.format_exc ()))
        mobius.core.logf (traceback.format_exc ())

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Add password to model
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def add_password (self, p):
    self.__passwords.append (p)

    for ant in self.__ants:
      try:
        f = getattr (ant, 'on_add_password', None)

        if f:
          mobius.core.logf ('DBG ant.on_add_password started: %s' % ant.name)
          f (self, p)
          mobius.core.logf ('DBG ant.on_add_password ended: %s' % ant.name)
      except Exception as e:
        mobius.core.logf ('WRN (%s.on_add_password) %s %s' % (ant.name, str (e), traceback.format_exc ()))

    self.test_password (p.value)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Test password
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def test_password (self, value):
    if isinstance (value, bytes):
      mobius.core.logf ('WRN bytes password %s' % '\n'.join (traceback.format_stack ()))

    if value in self.__unique_passwords:
      return

    self.__unique_passwords.add (value)

    for ant in self.__ants:
      try:
        f = getattr (ant, 'on_test_password', None)

        if f:
          mobius.core.logf ('DBG ant.on_test_password started <%s> %s' % (value, ant.name))
          f (self, value)
          mobius.core.logf ('DBG ant.on_test_password ended <%s> %s' % (value, ant.name))
      except Exception as e:
        mobius.core.logf ('WRN (%s.on_test_password) %s %s' % (ant.name , str (e), traceback.format_exc ()))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Add hash to model
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def add_hash (self, h):
    self.__hashes.append (h)

    for ant in self.__ants:
      f = getattr (ant, 'on_hash', None)

      if f:
        f (self, h)
