#!/usr/bin/env python
import argparse
import sys
import os
import biplist
from iOSbackup import iOSbackup

"""
Python 3 script to search for presence of a term in a iOS encrypted backup.

dependencies:
    pip install biplist iOSbackup 
"""

def decrypt_backup(dev_udid, backup_home, passwd):
    backup_object = None
    try:
        backup_object = iOSbackup(udid=dev_udid, cleartextpassword=passwd, backuproot=backup_home)
    except:
        backup_object = None
    return backup_object

def search_term(backup_object, term, case_insensitive, save_files_content_found):
    file_id = []
    searched_term = term
    if case_insensitive:
        searched_term = term.lower()
    for item in backup_object.getBackupFilesList():
        print(f"\rProcessing: {item['fileID']:<41}", end="", flush=True)
        content = ""
        if len(item["relativePath"]) == 0:
            continue
        clear_file_ref = backup_object.getFileDecryptedCopy(relativePath=item["relativePath"], temporary=True) 
        if clear_file_ref["isFolder"] or clear_file_ref["size"] == 0:
            continue
        clear_file = clear_file_ref["decryptedFilePath"]
        if clear_file.endswith(".plist"):
            try:
                with open(clear_file, "rb") as f:
                    content = biplist.readPlistFromString(f.read())
            except biplist.InvalidPlistException:
                with open(clear_file, "r", encoding="utf-8") as f:
                    content = f.read()    
        else:
            try:
                with open(clear_file, "r", encoding="utf-8") as f:
                    content = f.read()
            except:
                pass
        seachable_content = str(content)
        if case_insensitive:
            seachable_content = seachable_content.lower()
        if searched_term in seachable_content:
            file_id.append((item["fileID"][0:2] + os.path.sep + item["fileID"], item["relativePath"], clear_file_ref["size"]))
            if save_files_content_found:
                with open(item["fileID"] + ".txt", "w") as f:
                    f.write(str(content))
    return file_id

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Script to search for presence of a term in a iOS encrypted backup.", epilog="Call example: python search-in-encrypted-ios-backup.py --udid 00000000-0000000000000000 --backup-home '/work/ios-bkp-export' --password 'MyPwd' --term 'secret' --case-insensitive --save-files-content-found")
    required_params = parser.add_argument_group("required named arguments")
    required_params.add_argument("--udid", action="store", dest="udid", type=str, help="Device UDID", required=True)    
    required_params.add_argument("--backup-home", action="store", dest="backup_home", type=str, help="Backup home folder", required=True)
    required_params.add_argument("--password", action="store", dest="passwd", type=str, help="Backup protection password", required=True)
    required_params.add_argument("--term", action="store", dest="term", type=str, help="Searched term", required=True)   
    parser.add_argument("--case-insensitive", action="store_true", dest="case_insensitive", help="Search in case insensitive mode", required=False)
    parser.add_argument("--save-files-content-found", action="store_true", dest="save_files_content_found", help="Save in the current folder content for files found (format 'FILEID'.txt).", required=False)  
    args = parser.parse_args()
    device_udid = args.udid
    backup_root = args.backup_home
    passwd = args.passwd
    term = args.term
    case_insensitive = args.case_insensitive
    save_files_content_found = args.save_files_content_found
    print("[+] Decrypting backup...")
    b = decrypt_backup(device_udid, backup_root, passwd)
    if b is None:
        print("Invalid password or parameters!")
        sys.exit(1)
    print(f"[+] Case insensitive search: {case_insensitive} - Save files found: {save_files_content_found} - Searching for file containing the term '{term}'...")
    files = search_term(b, term, case_insensitive, save_files_content_found)
    print("")
    if len(files) > 0:
        print(f"[+] Content found in the following {len(files)} files:")
        for f in files:
            print(f"{f[0]} - {f[1]} ({f[2]} bytes)")
    else:
        print("[+] Content not found!")
    exit(len(files))
