-
Notifications
You must be signed in to change notification settings - Fork 7
/
UUIDFinder.py
executable file
·227 lines (189 loc) · 8 KB
/
UUIDFinder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/usr/bin/env python3
import os
import json
import subprocess
import argparse
from typing import List, Optional, Dict
home_directory = os.path.expanduser("~")
DEFAULT_DATABASE_FILE = os.path.join(home_directory, '.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()