From 2a221e77b148bdb223f89d61882dd1741c3c339e Mon Sep 17 00:00:00 2001 From: Karmaz95 Date: Tue, 29 Oct 2024 22:35:29 +0100 Subject: [PATCH] Uploading UUIDFinder tool --- IX. TCC/python/UUIDFinder.py | 225 +++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 IX. TCC/python/UUIDFinder.py diff --git a/IX. TCC/python/UUIDFinder.py b/IX. TCC/python/UUIDFinder.py new file mode 100644 index 0000000..cd51c55 --- /dev/null +++ b/IX. TCC/python/UUIDFinder.py @@ -0,0 +1,225 @@ +import os +import json +import subprocess +import argparse +from typing import List, Optional, Dict + +DEFAULT_DATABASE_FILE = 'uuid_database.json' + +class UUIDFinder: + def __init__(self, db_location: str = DEFAULT_DATABASE_FILE): + """Initialize UUIDFinder with database location.""" + self.db_location = db_location + self.database = self.load_database() + + def load_database(self) -> dict: + """Load the UUID database from a JSON file.""" + if os.path.exists(self.db_location): + with open(self.db_location, 'r') as file: + return json.load(file) + return {} + + def save_database(self): + """Save the UUID database to a JSON file.""" + with open(self.db_location, 'w') as file: + json.dump(self.database, file, indent=4) + + def extract_uuids(self, path: str) -> List[str]: + """Extract UUIDs from a Mach-O file using dwarfdump.""" + uuids = [] + try: + result = subprocess.run(['dwarfdump', '--uuid', path], + capture_output=True, text=True) + if result.returncode == 0: + for line in result.stdout.splitlines(): + if 'UUID:' in line: + uuid = line.split(':')[1].strip().split()[0].lower() + uuids.append(uuid) + except Exception as e: + print(f"Error extracting UUIDs from {path}: {e}") + return uuids + + def get_path_uuids(self, path: str) -> Optional[List[str]]: + """Get UUIDs for a given path from the database.""" + return self.database.get(path) + + def get_path_by_uuid(self, uuid: str) -> Optional[str]: + """Find path corresponding to a given UUID.""" + uuid = uuid.lower() + for path, uuids in self.database.items(): + if uuid in uuids: + return path + return None + + def handle_path_uuid(self, path: str, uuid: str): + """Handle path and UUID combination for database operations.""" + absolute_path = os.path.abspath(path) + uuid = uuid.lower() + + if absolute_path in self.database: + if uuid in self.database[absolute_path]: + print(f"Record with UUID {uuid} already exists for {absolute_path}") + else: + print(f"Adding UUID {uuid} to existing record for {absolute_path}") + self.database[absolute_path].append(uuid) + else: + print(f"Creating new record for {absolute_path} with UUID {uuid}") + self.database[absolute_path] = [uuid] + + def delete_path(self, path: str): + """Delete a path and its UUIDs from the database.""" + absolute_path = os.path.abspath(path) + if absolute_path in self.database: + print(f"Deleting record for: {absolute_path}") + del self.database[absolute_path] + else: + print(f"No record found for: {absolute_path}") + + def resolve_uuid(self, path: str): + """Get UUIDs for a path and add them to the database.""" + absolute_path = os.path.abspath(path) + if not os.path.isfile(absolute_path) or not os.access(absolute_path, os.X_OK): + print(f"Invalid path or not an executable: {absolute_path}") + return + + uuids = self.extract_uuids(absolute_path) + if uuids: + print(f"{absolute_path}: {', '.join(uuids)}") + self.database[absolute_path] = uuids + else: + print(f"No UUIDs found in {absolute_path}") + + def show_database(self): + """Display all records in the database.""" + if not self.database: + print("Database is empty") + return + + print("\nDatabase contents:") + print("-----------------") + for path, uuids in self.database.items(): + print(f"{path}", end="") + print(f"{', '.join(uuids)}") + print("\n-----------------") + +def process_paths(args): + """Process paths based on provided arguments.""" + finder = UUIDFinder(args.db_location) + + # Handle show_db flag + if args.show_db: + finder.show_database() + return + + # Handle UUID lookup without path + if args.uuid and not args.path and not args.list: + path = finder.get_path_by_uuid(args.uuid) + if path: + print(f"Path for UUID {args.uuid}: {path}") + else: + print(f"No path found for UUID: {args.uuid}") + return + + paths = [] + if args.path: + paths = [args.path] + elif args.list: + if os.path.isfile(args.list): + with open(args.list, 'r') as file: + paths = [line.strip() for line in file if line.strip()] + else: + print(f"Invalid list file: {args.list}") + return + + for path in paths: + absolute_path = os.path.abspath(path) + + if args.delete: + finder.delete_path(absolute_path) + elif args.uuid: + finder.handle_path_uuid(absolute_path, args.uuid) + elif args.resolve: + finder.resolve_uuid(absolute_path) + else: + # Default behavior: display UUIDs for the path + uuids = finder.get_path_uuids(absolute_path) + if uuids: + print(f"UUIDs for {absolute_path}: {', '.join(uuids)}") + else: + print(f"No UUIDs found for {absolute_path}") + + finder.save_database() + +def main(): + parser = argparse.ArgumentParser( + description='UUIDFinder - A tool for managing Mach-O executable UUIDs', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" +Examples: +--------- + +1. Display UUIDs for a single executable from database: + --path /path/to/executable + -p /path/to/executable + +2. Find path for a specific UUID in database: + --uuid 123e4567-e89b-12d3-a456-426614174000 + -u 123e4567-e89b-12d3-a456-426614174000 + +3. Add or update UUID for a path: + --path /path/to/executable --uuid 123e4567-e89b-12d3-a456-426614174000 + -p /path/to/executable -u 123e4567-e89b-12d3-a456-426614174000 + +4. Extract and add UUIDs from executable to database: + --path /path/to/executable --resolve + -p /path/to/executable -r + +5. Delete path and its UUIDs from database: + --path /path/to/executable --delete + -p /path/to/executable -d + +6. Process multiple executables from a list file: + --list /path/to/list.txt --resolve + -l /path/to/list.txt -r + +7. Show all records in the database: + --show_db + -s + +8. Use custom database location: + --path /path/to/executable --db_location /custom/path/db.json + -p /path/to/executable --db_location /custom/path/db.json + +Notes: +------ +- All UUIDs are stored in lowercase in the database +- The default database file is 'uuid_database.json' in the current directory +- When using --list, each path should be on a new line in the list file +- The tool automatically converts relative paths to absolute paths +""") + + # Path specification group + path_group = parser.add_mutually_exclusive_group() + path_group.add_argument('--path', '-p', help='Path to the executable') + path_group.add_argument('--list', '-l', help='Path to a file containing a list of executables') + + # Action group + parser.add_argument('--uuid', '-u', help='UUID to lookup or add') + parser.add_argument('--delete', '-d', action='store_true', help='Delete the path record from database') + parser.add_argument('--resolve', '-r', action='store_true', help='Get UUIDs for the path and add to database') + parser.add_argument('--show_db', '-s', action='store_true', help='Show all records in the database') + + # Database location + parser.add_argument('--db_location', default=DEFAULT_DATABASE_FILE, + help='Location of the UUID database file') + + args = parser.parse_args() + + # Validate that at least one argument is provided + if not any([args.path, args.list, args.show_db, args.uuid]): + parser.error("At least one of --path, --list, --show_db, or --uuid is required") + + process_paths(args) + +if __name__ == "__main__": + main() \ No newline at end of file