class SAPCredv2_Cred_Plain(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_IA5_STRING("pin", None), ASN1F_optional(ASN1F_IA5_STRING("option1", None)), ASN1F_optional(ASN1F_IA5_STRING("option2", None)), ASN1F_optional(ASN1F_IA5_STRING("option3", None)), ) def decrypt_provider(self, cred): """Decrypts a credential file already decrypted using the specified provider. This is platform dependent and requires specific third-party libraries. :param cred: credential from where the blob was extracted :type cred: SAPCredv2_Cred :return: the content in the blob decrypted using the provider :rtype: string :raise Exception: if the provider is invalid or unsupported """ if self.option1 and self.option1 in self.providers: return self.providers[self.option1](self, cred) else: raise Exception("Invalid or unsupported provider") @staticmethod def decrypt_MSCryptProtect(plain, cred): """Decrypts a credential using the Windows DP API. Requires the current logged-in user to have permissions to decrypt the blob stored in the credentials file. :param plain: plain credential extracted :type plain: SAPCredv2_Cred_Plain :param cred: credential from where the blob was extracted :type cred: SAPCredv2_Cred :return: the content in the blob decrypted using the provider :rtype: string """ entropy = cred.pse_path return dpapi_decrypt_blob(unhexlify(plain.blob.val), entropy) PROVIDER_MSCryptProtect = "MSCryptProtect" """Provider for Windows hosts using DPAPI""" providers = { PROVIDER_MSCryptProtect: decrypt_MSCryptProtect, } """Definition of implemented providers"""
class ASN1P_PRIVSEQ(ASN1_Packet): # This class gets used in x509.uts # It showcases the private high-tag decoding capacities of scapy. ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE(ASN1F_IA5_STRING("str", ""), ASN1F_STRING("int", 0), explicit_tag=0, flexible_tag=True)
class X509_URI(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_IA5_STRING("uniformResourceIdentifier", "")
class X509_DNSName(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_IA5_STRING("dNSName", "")
class X509_RFC822Name(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_IA5_STRING("rfc822Name", "")
class SAPCredv2_Cred(ASN1_Packet): """SAP Credv2 Credential without LPS definition""" ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_SEQUENCE( ASN1F_IA5_STRING("cert_name", None), ASN1F_IA5_STRING("unknown1", None), ASN1F_IA5_STRING("pse_path", None), ASN1F_IA5_STRING("unknown2", None), ASN1F_BIT_STRING("cipher", None), ) @property def common_name(self): return self.cert_name.val @property def pse_file_path(self): return self.pse_path.val @property def lps_type(self): return None @property def lps_type_str(self): return "OFF" @property def cipher_format_version(self): cipher = self.cipher.val_readable if len(cipher) >= 36 and ord(cipher[0]) in [0, 1]: return ord(cipher[0]) return 0 @property def cipher_algorithm(self): if self.cipher_format_version == 1: return ord(self.cipher.val_readable[1]) return 0 def decrypt(self, username): """Decrypt a credential given a particular username. Tries to identify the credential format and choose the decryption method to use. :param username: Username to use when decrypting :type username: string :return: decrypted object :rtype: SAPCredv2_Cred_Plain """ if self.cipher_format_version == 1: return self.decrypt_with_header(username) else: return self.decrypt_simple(username) def decrypt_simple(self, username): """Decrypt a credential using the simple approach. It only handles 3DES. Tries to parse the decrypted object into a plain credential object type. If it fails, probably due to an invalid username use to decrypt it, raises an exception. :param username: Username to use when decrypting :type username: string :return: decrypted object :rtype: SAPCredv2_Cred_Plain """ blob = self.cipher.val_readable # Construct the key using the key format and the username key = (cred_key_fmt % username)[:24] # Set empty IV iv = "\x00" * 8 # Decrypt the cipher text with the derived key and IV decryptor = Cipher(algorithms.TripleDES(key), modes.CBC(iv), backend=default_backend()).decryptor() plain = decryptor.update(blob) + decryptor.finalize() return SAPCredv2_Cred_Plain(plain) def decrypt_with_header(self, username): """Decrypt a credential file using the header. It handles 3DES and AES256 algorithms. Tries to parse the decrypted object into a plain credential object type. If it fails, probably due to an invalid username use to decrypt it, raises an exception. :param username: Username to use when decrypting :type username: string :return: decrypted object :rtype: SAPCredv2_Cred_Plain :raise SAPCredv2_Decryption_Error: if there's an error decrypting the object """ blob = self.cipher.val_readable header = SAPCredv2_Cred_Cipher(blob) # Validate supported version if header.version != 1: raise SAPCredv2_Decryption_Error("Version not supported") # Validate and select proper algorithm if header.algorithm == CIPHER_ALGORITHM_3DES: algorithm = algorithms.TripleDES elif header.algorithm == CIPHER_ALGORITHM_AES256: algorithm = algorithms.AES else: raise SAPCredv2_Decryption_Error("Algorithm not supported") def xor(string, start): """XOR a given string using a fixed key and a starting number.""" key = 0x15a4e35 x = start y = "" for c in string: x *= key x += 1 y += chr(ord(c) ^ (x & 0xff)) return y def derive_key(key, header, salt, username): """Derive a key using SAP's algorithm. The key is derived using SHA256 and xor from an initial key, a header, salt and username. """ digest = Hash(SHA256(), backend=default_backend()) digest.update(key) digest.update(header) digest.update(salt) digest.update(xor(username, ord(salt[0]))) digest.update("" * 0x20) hashed = digest.finalize() derived_key = xor(hashed, ord(salt[1])) return derived_key # Derive the key using SAP's algorithm key = derive_key(cred_key_fmt, blob[0:4], header.salt, username) # Decrypt the cipher text with the derived key and IV decryptor = Cipher(algorithm(key), modes.CBC(header.iv), backend=default_backend()).decryptor() plain = decryptor.update(header.cipher_text) + decryptor.finalize() # Perform a final xor over the decrypted content with a fixed key plain = xor(plain, 0x64FB914E) return SAPCredv2_Cred_Plain(plain)