Skip to content

Instantly share code, notes, and snippets.

@xperylab
Created October 15, 2021 15:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xperylab/7d54e35aed918c768eeb0787f6e2c118 to your computer and use it in GitHub Desktop.
Save xperylab/7d54e35aed918c768eeb0787f6e2c118 to your computer and use it in GitHub Desktop.
PM : decrypt attachments
import pgpy
import pandas
import sqlite3
from Crypto.cipher import AES
import os
from base64 import b64decode
import plistllib
import ccl_bplist
IVsize = 16
# Decrypt values using main key and AES_CTR
def decryptWithMainKey(encrypted, mainKey):
iv = encrypted[:IVsize]
cipher = AES.new(mainKey, AES.MODE_CTR, initial_value=iv, nonce=b'')
return cipher.decrypt(encrypted[IVsize:])
# Decrypt an attachment
def decrypt_attachment(proton_path, out_path, key, pwdKey, keyPacket, encfilename, decfilename):
att = None
for r, _, f in os.walk(proton_path):
for file in f:
if encfilename in file:
att=os.path.join(r,file)
break
if att:
with key.unlock(pwdKey):
assert key.is_unlocked
with open(att, 'rb') as attfh:
buf = b64decode(keyPacket)
buf += attfh.read()
att_from_blob = pgpy.PGPMessage.from_blob(buf)
decatt = key.decrypt(att_from_blob).message
with open(os.path.join(out_path,decfilename),'wb') as outatt:
outatt.write(decatt)
return decfilename
return None
# Decrypt table Attachments and attachment itself
def decrypt_records_attachments(row, mainKey, proton_path, out_path, key, pwdKey):
if row['ZFILENAME']:
row['ZFILENAME'] = decryptWithMainKey(row['ZFILENAME'], mainKey)
if row['ZHEADERINFO']:
row['ZHEADERINFO'] = decryptWithMainKey(row['ZHEADERINFO'], mainKey)
if row['ZMIMETYPE']:
row['ZMIMETYPE'] = decryptWithMainKey(row['ZMIMETYPE'], mainKey)
if row['ZFILENAME']:
filename = plistlib.loads(row['ZFILENAME'])[0]
if row['ZLOCALURL']:
encfilename = plistlib.loads(row['ZLOCALURL'])['$objects'][2].split('/')[-1]
row['decrypted_file'] = decrypt_attachment(proton_path, out_path, key, pwdKey, row['ZKEYPACKET'], encfilename, filename)
row['content-type'] = plistlib.loads(row['ZMIMETYPE'])[0]
return row
def main():
proton_path = 'path/to/extraction'
out_path = 'path/to/output'
mainKey = 'decrypted with PMPinProtection or gathered directly from keychain'
with open('group.ch.protonmail.protonmail.plist','rb') as p :
prefplist = plistlib.load(p)
enc_val = prefplist['authKeychainStoreKeyProtectedWithMainKey']
dec_val = decryptWithMainKey(enc_val, mainKey)
keychainStorePlist1 = ccl_bplist.load(BytesIO(dec_val))
keychainStorePlist = ccl_bplist.load(BytesIO(keychainStorePlist1[0]))
keychainStore = ccl_bplist.deserialise_NsKeyedArchiver(keychainStorePlist, parse_whole_structure=True)
privateKeyCoderKey = keychainStore['root']['NS.objects'][0]['privateKeyCoderKey']
key, _ = pgpy.PGPKey.from_blob(privateKeyCoderKey)
pwdKey = keychainStore['root']['NS.objects'][0]['AuthCredential.Password']
with sqlite3.connect('ProtonMail.sqlite') as db:
df = pandas.read_sql_query("""
SELECT
*
FROM
ZATTACHMENT
""",db)
df = df.apply(decrypt_records_attachments, args=(mainKey, proton_path, out_path, key, pwdKey, ), axis=1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment