#!/usr/bin/env python

import binascii
import pymobius.registry.user_accounts
import pymobius.registry.cached_credentials
from pymobius.registry import *
import mobius

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

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

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, itemlist):
    self.__itemlist = [ item for item in itemlist if item.datasource ]
    self.__data = None

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief get model data
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_data (self):
    if not self.__itemlist:
      return None

    data = dataholder ()
    data.hashes = self.__get_hashes ()
    data.passwords = self.__get_passwords ()
    
    # add passwords from hashes
    turing = mobius.turing.turing ()

    for h in data.hashes:
      ht = turing.get_hash (h.type, h.value)

      if ht.password_status == 2:
        if h.type in ("nt", "lm"):
          description = "user %s logon password" % h.username

        elif h.type in ("msdcc1", "msdcc2"):
          description = "user %s cached credential password" % h.username

        if ((ht.password, description)) not in data.passwords:
          data.passwords.append ((ht.password, description))

    # return data
    return data

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief get password hashes
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_hashes (self):
    hashes = []
    turing = mobius.turing.turing ()

    for item in self.__itemlist:
      try:
        ant = pymobius.registry.main.Ant (item)
        reglist = ant.get_data ()
      
        for reg in reglist:
          hashes += self.__get_hashes_from_registry (reg)
      except Exception, e:
        print 'warning:', e

    return hashes
      
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief get passwords
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_passwords (self):
    passwords = []

    for item in self.__itemlist:
      try:
        ant = pymobius.registry.main.Ant (item)
        reglist = ant.get_data ()
      
        for reg in reglist:
          passwords += self.__get_passwords_from_registry (reg)
      except Exception, e:
        print 'warning:', e

    return passwords
      
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief get password hashes from registry
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_hashes_from_registry (self, registry):
    hashes = []
    turing = mobius.turing.turing ()
    transaction = turing.new_transaction ()

    # get user password hashes
    for account in pymobius.registry.user_accounts.get (registry):
      h_set = set ()

      for h in account.hashes:
        value = binascii.hexlify (h.value)
        k = (h.type, value, h.is_current)

        if k not in h_set:
          h_set.add (k)

          if h.is_current:
            description = 'password hash of user ' + account.username
          else:
            description = 'old password hash of user ' + account.username

          d = dataholder ()
          d.type = h.type
          d.value = value
          d.description = description
          d.username = account.username
          d.is_current = h.is_current
          hashes.append (d)

          ht = turing.new_hash (d.type, d.value)

    # get cached credential hashes
    for name, credential in pymobius.registry.cached_credentials.get (registry):

      if credential.encryption_algorithm == 0:
        t = 'msdcc1'

      elif credential.encryption_algorithm == 10:
        t = 'msdcc2'

      else:
        print 'Unknown credential encryption algorithm (%d)' % credential.encryption_algorithm
        t = None

      if t:
        value = binascii.hexlify (credential.mscachehash)
        d = dataholder ()
        d.type = t
        d.value = value
        d.description = 'cached credential hash of user ' + credential.username
        d.username = credential.username
        hashes.append (d)

        ht = turing.new_hash (d.type, d.value)
        ht.set_argument ("username", d.username)

    transaction.commit ()
    return hashes

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief get passwords from registry
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __get_passwords_from_registry (self, registry):
    passwords = []
    turing = mobius.turing.turing ()
    transaction = turing.new_transaction ()

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # get passwords from LSA secrets
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    for key in registry.get_key_by_mask ('\\HKLM\\SECURITY\\Policy\\Secrets\\*'):
          
      # @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa378826(v=vs.85).aspx
      if key.name == 'DefaultPassword':
        description = 'automatic logon'
      
      elif key.name == '0083343a-f925-4ed7-b1d6-d95d17a0b57b-RemoteDesktopHelpAssistantAccount':
        description = 'user HelpAssistant logon'

      elif key.name == 'aspnet_WP_PASSWORD':
        description = 'user ASPNET logon'

      elif key.name == 'SCM:{3D14228D-FBE1-11D0-995D-00C04FD919C1}':
        description = 'IIS IWAM'

      # @see http://nvidia.custhelp.com/app/answers/detail/a_id/3067/~/what-is-nvidia-%E2%80%99updatususer%E2%80%99%3F
      elif key.name == '_SC_nvUpdatusService':
        description = 'user UpdatusUser logon'

      elif key.name.startswith ('_SC_DB2'):
        description = 'DB2'

      elif key.name.startswith ('_SC_postgresql-') or key.name.startswith ('_SC_pgsql-'):
        description = 'PostgreSQL'

      else:
        description = None

      # add current and old passwords
      if description:
        currval = get_data_as_string (key.get_data_by_path ('Currval\\(default)'))
        oldval = get_data_as_string (key.get_data_by_path ('Oldval\\(default)'))

        if currval:
          passwords.append ((currval, description + ' password'))
          turing.test_keyword (currval)

        if oldval:
          passwords.append ((oldval, description + ' old password'))
          turing.test_keyword (oldval)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # get passwords for Outlook Express and Outlook 98-2000
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    for user_key in registry.get_key_by_mask ('\\HKU\\*'):
      sid = user_key.name
      pssp_key = user_key.get_key_by_path ('Software\\Microsoft\\Protected Storage System Provider\\' + sid)
 
      for subkey in user_key.get_key_by_mask ('Software\\Microsoft\\Internet Account Manager\\Accounts\*'):
        account_name = get_data_as_string (subkey.get_data_by_name ('SMTP Display Name'))

        if account_name:
          email_address = get_data_as_string (subkey.get_data_by_name ('SMTP Email Address'))

          # passwords
          pop3_value_name = subkey.get_data_by_name ('POP3 Password2')
          pop3_password = self.__get_pssp_password (pssp_key, pop3_value_name)

          if pop3_password:
            passwords.append ((pop3_password, "E-mail " + email_address + " POP3 password"))
            turing.test_keyword (pop3_password)

          smtp_value_name = subkey.get_data_by_name ('SMTP Password2')
          smtp_password = self.__get_pssp_password (pssp_key, smtp_value_name)

          if smtp_password:
            passwords.append ((smtp_password, "E-mail " + email_address + " SMTP password"))
            turing.test_keyword (smtp_password)

          http_value_name = subkey.get_data_by_name ('HTTPMail Password2')
          http_password = self.__get_pssp_password (pssp_key, http_value_name)

          if http_password:
            passwords.append ((http_password, "E-mail " + email_address + " HTTP password"))
            turing.test_keyword (http_password)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # return passwords found
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    transaction.commit ()
    return passwords

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief get Protected Storage password
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 
  def __get_pssp_password (self, pssp_key, idx_data):
    password = None
    
    if pssp_key and idx_data:
      value_name = unicode (idx_data.data[2:], 'utf-16-le').rstrip ('\0').encode ('utf-8')
      data = pssp_key.get_data_by_path ('Data\\220d5cc1-853a-11d0-84bc-00c04fd43f8f\\417e2d75-84bd-11d0-84bb-00c04fd43f8f\\' + value_name)

      if data:
        password = data.data.rstrip ('\0')

    return password
