From c6b091f7f741e8fe968fc309ef2a18dec0d037b4 Mon Sep 17 00:00:00 2001 From: ARIEF NOOR ROCHMATULLAH Date: Fri, 28 Nov 2025 11:18:41 +0700 Subject: [PATCH 1/3] Fix Bug Google Maps GMM Parsing Errors --- scripts/artifacts/googleMapsGmm.py | 245 ++++++++++++++++++----------- 1 file changed, 157 insertions(+), 88 deletions(-) diff --git a/scripts/artifacts/googleMapsGmm.py b/scripts/artifacts/googleMapsGmm.py index 991cb3db..26608db6 100644 --- a/scripts/artifacts/googleMapsGmm.py +++ b/scripts/artifacts/googleMapsGmm.py @@ -21,109 +21,178 @@ from scripts.artifact_report import ArtifactHtmlReport from scripts.ilapfuncs import logfunc, tsv, timeline, kmlgen, is_platform_windows, open_sqlite_db_readonly, convert_utc_human_to_timezone +def get_table_names(cursor): + """Get all table names from database""" + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + return [row[0] for row in cursor.fetchall()] + def get_googleMapsGmm(files_found, report_folder, seeker, wrap_text): data_list_storage = [] data_list_myplaces = [] + file_found_storage = None + file_found_myplaces = None for file_found in files_found: file_found = str(file_found) + if file_found.endswith('gmm_storage.db'): - db = open_sqlite_db_readonly(file_found) - file_found_storage = file_found - cursor = db.cursor() - cursor.execute(''' - select - rowid, - _data, - _key_pri - from gmm_storage_table - ''') - all_rows = cursor.fetchall() - for row in all_rows: - id = row[0] - data = row[1] - keypri = row[2] - idx=data.find(b"/dir/") + try: + db = open_sqlite_db_readonly(file_found) + file_found_storage = file_found + cursor = db.cursor() + cursor.execute(''' + select + rowid, + _data, + _key_pri + from gmm_storage_table + ''') + all_rows = cursor.fetchall() - if (idx!=-1): - length=struct.unpack("1 and len(dd.split("!2d"))>1: - tolon=dd.split("!1d")[1].split("!")[0] - tolat=dd.split("!2d")[1].split("!")[0] - idx=data.find(b"\x4C\x00\x01\x67\x74\x00\x12\x4C\x6A\x61\x76\x61\x2F\x6C\x61\x6E\x67\x2F\x53\x74\x72\x69\x6E\x67\x3B\x78\x70") if (idx!=-1): - timestamp=struct.unpack(">Q",data[idx+0x1B:idx+0x1B+8])[0] - - if directions.startswith('b\''): - directions = directions.replace('b\'','', 1) - directions = directions[:-1] - - directions = ("https://google.com/maps"+directions) - directions = f'{directions}' - data_list_storage.append((directions, fromlat, fromlon, tolat, tolon, id, keypri)) - db.close() + length=struct.unpack("1 and len(dd.split("!2d"))>1: + tolon=dd.split("!1d")[1].split("!")[0] + tolat=dd.split("!2d")[1].split("!")[0] + idx=data.find(b"\x4C\x00\x01\x67\x74\x00\x12\x4C\x6A\x61\x76\x61\x2F\x6C\x61\x6E\x67\x2F\x53\x74\x72\x69\x6E\x67\x3B\x78\x70") + if (idx!=-1): + timestamp=struct.unpack(">Q",data[idx+0x1B:idx+0x1B+8])[0] + + if directions.startswith('b\''): + directions = directions.replace('b\'','', 1) + directions = directions[:-1] + + directions = ("https://google.com/maps"+directions) + directions = f'{directions}' + data_list_storage.append((directions, fromlat, fromlon, tolat, tolon, id, keypri)) + db.close() + except Exception as e: + logfunc(f'[!] Error processing gmm_storage.db: {str(e)}') if file_found.endswith('gmm_myplaces.db'): - db = open_sqlite_db_readonly(file_found) - file_found_myplaces = file_found - cursor = db.cursor() - cursor.execute(''' - select - rowid, - key_string, - round(latitude*.000001,6), - round(longitude*.000001,6), - sync_item, - timestamp - from sync_item - ''') - all_rows = cursor.fetchall() - - for row in all_rows: - id = row[0] - keystring = row[1] - latitude = row[2] - longitude = row[3] - syncitem = row[4] - timestamp = row[5] - pb = blackboxprotobuf.decode_message(syncitem, 'None') - - if keystring == "0:0": - label = "Home" - elif keystring == "1:0": - label = "Work" + try: + db = open_sqlite_db_readonly(file_found) + file_found_myplaces = file_found + cursor = db.cursor() + + # Check available tables + tables = get_table_names(cursor) + logfunc(f'Available tables in gmm_myplaces.db: {tables}') + + # Try different possible table names + table_name = None + if 'sync_item' in tables: + table_name = 'sync_item' + elif 'places' in tables: + table_name = 'places' + elif 'favorites' in tables: + table_name = 'favorites' else: - label = pb[0].get('6', {}).get('7', b'').decode('utf-8') + # Use first available table if known tables not found + if tables: + table_name = tables[0] + logfunc(f'Using table: {table_name} (sync_item not found)') + else: + logfunc('No tables found in gmm_myplaces.db') + db.close() + continue + + # Query based on available table + try: + cursor.execute(f''' + select + rowid, + key_string, + round(latitude*.000001,6), + round(longitude*.000001,6), + sync_item, + timestamp + from {table_name} + ''') + except sqlite3.OperationalError as e: + logfunc(f'Error querying {table_name}: {str(e)}') + db.close() + continue + + all_rows = cursor.fetchall() - address = pb[0].get('6', {}).get('2', b'').decode('utf-8') - url = pb[0].get('6', {}).get('6', b'').decode('utf-8') - url = f'{url}' - timestamp = datetime.fromtimestamp(timestamp/1000, tz=timezone.utc) - timestamp = convert_utc_human_to_timezone(timestamp, 'UTC') - data_list_myplaces.append((timestamp,label,latitude,longitude,address,url)) - db.close() - else: - continue + for row in all_rows: + try: + id = row[0] + keystring = row[1] + latitude = row[2] + longitude = row[3] + syncitem = row[4] + timestamp = row[5] + + # Handle cases where syncitem might be None or invalid + if syncitem is None: + label = "Unknown" + address = "" + url = "" + else: + try: + pb = blackboxprotobuf.decode_message(syncitem, 'None') + + if keystring == "0:0": + label = "Home" + elif keystring == "1:0": + label = "Work" + else: + label = pb[0].get('6', {}).get('7', b'').decode('utf-8', errors='ignore') + + address = pb[0].get('6', {}).get('2', b'').decode('utf-8', errors='ignore') + url = pb[0].get('6', {}).get('6', b'').decode('utf-8', errors='ignore') + except Exception as e: + logfunc(f'Error decoding protobuf for row {id}: {str(e)}') + label = "Unknown" + address = "" + url = "" + + if url: + url = f'{url}' + + if timestamp: + try: + timestamp = datetime.fromtimestamp(timestamp/1000, tz=timezone.utc) + timestamp = convert_utc_human_to_timezone(timestamp, 'UTC') + except: + timestamp = "" + + data_list_myplaces.append((timestamp, label, latitude, longitude, address, url)) + except Exception as e: + logfunc(f'Error processing row {id}: {str(e)}') + continue + + db.close() + except Exception as e: + logfunc(f'[!] Error processing gmm_myplaces.db: {str(e)}') - if data_list_storage: + if data_list_storage and file_found_storage: report = ArtifactHtmlReport('Google Search History Maps') report.start_artifact_report(report_folder, 'Google Search History Maps') report.add_script() @@ -136,7 +205,7 @@ def get_googleMapsGmm(files_found, report_folder, seeker, wrap_text): else: logfunc('No Google Search History Maps data available') - if data_list_myplaces: + if data_list_myplaces and file_found_myplaces: report = ArtifactHtmlReport('Google Maps Label Places') report.start_artifact_report(report_folder, 'Google Maps Label Places') report.add_script() From 3e69500bbe1e6536f01766a25f8114ec80cbcee6 Mon Sep 17 00:00:00 2001 From: ARIEF NOOR ROCHMATULLAH Date: Fri, 28 Nov 2025 14:04:13 +0700 Subject: [PATCH 2/3] Fix Improved Google Keep Notes Artifact Parsing --- scripts/artifacts/keepNotes.py | 189 ++++++++++++++++++++++++++------- 1 file changed, 150 insertions(+), 39 deletions(-) diff --git a/scripts/artifacts/keepNotes.py b/scripts/artifacts/keepNotes.py index a50ec7b3..3d27efb1 100644 --- a/scripts/artifacts/keepNotes.py +++ b/scripts/artifacts/keepNotes.py @@ -16,51 +16,162 @@ import sqlite3 import datetime import os - from scripts.artifact_report import ArtifactHtmlReport from scripts.ilapfuncs import logfunc, tsv, timeline, open_sqlite_db_readonly +def get_table_names(cursor): + """Get all table names from database""" + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + return [row[0] for row in cursor.fetchall()] + +def get_column_names(cursor, table_name): + """Get all column names from a table""" + try: + cursor.execute(f"PRAGMA table_info({table_name});") + return [row[1] for row in cursor.fetchall()] + except: + return [] + def get_keepNotes(files_found, report_folder, seeker, wrap_text): for file_found in files_found: file_found = str(file_found) filename = os.path.basename(file_found) - + if filename.endswith('keep.db'): - db = open_sqlite_db_readonly(file_found) - cursor = db.cursor() - cursor.execute(''' - SELECT - datetime(tree_entity.time_created/1000, 'unixepoch') AS "Time Created", - datetime(tree_entity.time_last_updated/1000, 'unixepoch') AS "Time Last Updated", - datetime(tree_entity.user_edited_timestamp/1000, 'unixepoch') AS "User Edited Timestamp", - tree_entity.title AS Title, - text_search_note_content_content.c0text AS "Text", - tree_entity.last_modifier_email AS "Last Modifier Email" - FROM text_search_note_content_content - INNER JOIN tree_entity ON text_search_note_content_content.docid = tree_entity._id - ''') - - all_rows = cursor.fetchall() - usageentries = len(all_rows) - - if usageentries > 0: + try: + db = open_sqlite_db_readonly(file_found) + cursor = db.cursor() + + # Get all available tables + tables = get_table_names(cursor) + logfunc(f'Available tables in keep.db: {tables}') + + all_rows = [] data_list = [] - for row in all_rows: - data_list.append(row) - - report = ArtifactHtmlReport('Google Keep Notes') - report.start_artifact_report(report_folder, 'Google Keep Notes') - report.add_script() - data_headers = ('Time Created', 'Time Last Updated', 'User Edited Timestamp', 'Title', 'Text', 'Last Modifier Email') - report.write_artifact_data_table(data_headers, data_list, file_found, html_escape=False) - report.end_artifact_report() - - tsvname = 'Google Keep Notes' - tsv(report_folder, data_headers, data_list, tsvname) - - tlactivity = 'Google Keep Notes' - timeline(report_folder, tlactivity, data_list, data_headers) - - else: - logfunc('No Google Keep Notes data available') - + + # Try different query approaches based on available tables + query_success = False + + # Approach 1: Try original query structure + if 'text_search_note_content_content' in tables and 'tree_entity' in tables: + try: + logfunc('Attempting original query structure...') + cursor.execute(''' + SELECT + datetime(tree_entity.time_created/1000, 'unixepoch') AS "Time Created", + datetime(tree_entity.time_last_updated/1000, 'unixepoch') AS "Time Last Updated", + datetime(tree_entity.user_edited_timestamp/1000, 'unixepoch') AS "User Edited Timestamp", + tree_entity.title AS Title, + text_search_note_content_content.c0text AS "Text", + tree_entity.last_modifier_email AS "Last Modifier Email" + FROM text_search_note_content_content + INNER JOIN tree_entity ON text_search_note_content_content.docid = tree_entity._id + ''') + all_rows = cursor.fetchall() + query_success = True + logfunc('Original query structure successful') + except Exception as e: + logfunc(f'Original query failed: {str(e)}') + + # Approach 2: Try alternative structure if Approach 1 failed + if not query_success and 'Note' in tables: + try: + logfunc('Attempting Note table query...') + columns = get_column_names(cursor, 'Note') + logfunc(f'Note table columns: {columns}') + + cursor.execute(''' + SELECT + datetime(Note.create_time/1000, 'unixepoch') AS "Time Created", + datetime(Note.update_time/1000, 'unixepoch') AS "Time Last Updated", + datetime(Note.user_edited_timestamp/1000, 'unixepoch') AS "User Edited Timestamp", + Note.title AS Title, + Note.content AS "Text", + Note.owner_email AS "Last Modifier Email" + FROM Note + ''') + all_rows = cursor.fetchall() + query_success = True + logfunc('Note table query successful') + except Exception as e: + logfunc(f'Note table query failed: {str(e)}') + + # Approach 3: Try simple tree_entity table + if not query_success and 'tree_entity' in tables: + try: + logfunc('Attempting tree_entity table query...') + columns = get_column_names(cursor, 'tree_entity') + logfunc(f'tree_entity table columns: {columns}') + + cursor.execute(''' + SELECT + datetime(time_created/1000, 'unixepoch') AS "Time Created", + datetime(time_last_updated/1000, 'unixepoch') AS "Time Last Updated", + datetime(user_edited_timestamp/1000, 'unixepoch') AS "User Edited Timestamp", + title AS Title, + text AS "Text", + last_modifier_email AS "Last Modifier Email" + FROM tree_entity + WHERE type = 1 + ''') + all_rows = cursor.fetchall() + query_success = True + logfunc('tree_entity table query successful') + except Exception as e: + logfunc(f'tree_entity table query failed: {str(e)}') + + # Approach 4: Query any table with note data + if not query_success: + try: + logfunc('Attempting fallback dynamic query...') + # Try to find table with title and content columns + for table in tables: + if table.startswith('sqlite_'): + continue + columns = get_column_names(cursor, table) + if any(col in columns for col in ['title', 'content', 'text']): + logfunc(f'Attempting to query table: {table}') + cursor.execute(f'SELECT * FROM {table} LIMIT 1') + test_row = cursor.fetchone() + if test_row: + logfunc(f'Using table: {table}') + cursor.execute(f'SELECT * FROM {table}') + all_rows = cursor.fetchall() + query_success = True + break + except Exception as e: + logfunc(f'Fallback query failed: {str(e)}') + + # If we got data, process it + if all_rows and len(all_rows) > 0: + data_list = list(all_rows) + + report = ArtifactHtmlReport('Google Keep Notes') + report.start_artifact_report(report_folder, 'Google Keep Notes') + report.add_script() + + # Determine headers based on query result structure + if len(data_list[0]) == 6: + data_headers = ('Time Created', 'Time Last Updated', 'User Edited Timestamp', 'Title', 'Text', 'Last Modifier Email') + else: + # Use generic headers if structure is different + data_headers = tuple(f'Column_{i}' for i in range(len(data_list[0]))) + logfunc(f'Using generic headers due to non-standard data structure: {data_headers}') + + report.write_artifact_data_table(data_headers, data_list, file_found, html_escape=False) + report.end_artifact_report() + + tsvname = 'Google Keep Notes' + tsv(report_folder, data_headers, data_list, tsvname) + + tlactivity = 'Google Keep Notes' + timeline(report_folder, tlactivity, data_list, data_headers) + + logfunc(f'Found {len(data_list)} Google Keep Notes') + else: + logfunc('No Google Keep Notes data available or unable to parse database') + + db.close() + + except Exception as e: + logfunc(f'[!] Error processing keep.db: {str(e)}') \ No newline at end of file From 210884f147cea60cbb238b5023fe4cc462221603 Mon Sep 17 00:00:00 2001 From: ARIEF NOOR ROCHMATULLAH Date: Fri, 28 Nov 2025 15:02:42 +0700 Subject: [PATCH 3/3] Fix Resolve crashes in Turbo Artifacts & Update Battery Saver Logic --- .../artifacts/deviceHealthServices_Battery.py | 187 +++++++++++------- 1 file changed, 121 insertions(+), 66 deletions(-) diff --git a/scripts/artifacts/deviceHealthServices_Battery.py b/scripts/artifacts/deviceHealthServices_Battery.py index 38f2c8df..3ea7cbdb 100644 --- a/scripts/artifacts/deviceHealthServices_Battery.py +++ b/scripts/artifacts/deviceHealthServices_Battery.py @@ -37,94 +37,149 @@ from scripts.artifact_report import ArtifactHtmlReport from scripts.ilapfuncs import artifact_processor, open_sqlite_db_readonly, convert_ts_human_to_utc, convert_utc_human_to_timezone +#Ok tambahkan UTC utk timezone dan tambahkan battery saver untuk 0 enable, dan 2 disable @artifact_processor def Turbo_Battery(files_found, report_folder, seeker, wrap_text): source_file_turbo = '' - turbo_db = '' data_list = [] - + time_offset = 'UTC' + for file_found in files_found: file_found = str(file_found) - if file_found.lower().endswith('turbo.db'): - turbo_db = str(file_found) - source_file_turbo = os.path.basename(file_found) + if not file_found.lower().endswith('turbo.db'): + continue - db = open_sqlite_db_readonly(turbo_db) - cursor = db.cursor() - cursor.execute(''' - select - case timestamp_millis - when 0 then '' - else datetime(timestamp_millis/1000,'unixepoch') - End as D_T, - battery_level, - case charge_type - when 0 then '' - when 1 then 'Charging Rapidly' - when 2 then 'Charging Slowly' - when 3 then 'Charging Wirelessly' - End as C_Type, - case battery_saver - when 2 then '' - when 1 then 'Enabled' - End as B_Saver, - timezone - from battery_event - ''') - - all_rows = cursor.fetchall() - usageentries = len(all_rows) - if usageentries > 0: - for row in all_rows: - timestamp = row[0] - if timestamp is None: - pass - else: - timestamp = convert_utc_human_to_timezone(convert_ts_human_to_utc(timestamp),time_offset) - data_list.append((timestamp,row[1],row[2],row[3],row[4],file_found)) - - db.close() - - data_headers = (('Timestamp', 'datetime'),'Battery Level','Charge Type','Battery Saver','Timezone','Source') + #BugFix + source_file_turbo = os.path.basename(file_found) - return data_headers, data_list, source_file_turbo + try: + db = open_sqlite_db_readonly(file_found) + except Exception as e: + # Bisa ganti dengan logfunc jika ada + print(f"[!] Failed to open database {file_found}: {e}") + continue + + cursor = db.cursor() + cursor.execute(''' + SELECT + CASE timestamp_millis + WHEN 0 THEN '' + ELSE datetime(timestamp_millis/1000, 'unixepoch') + END AS D_T, + battery_level, + CASE charge_type + WHEN 0 THEN '' + WHEN 1 THEN 'Charging Rapidly' + WHEN 2 THEN 'Charging Slowly' + WHEN 3 THEN 'Charging Wirelessly' + END AS C_Type, + -- CASE battery_saver + -- WHEN 2 THEN '' + -- WHEN 1 THEN 'Enabled' + -- END AS B_Saver, + CASE battery_saver + WHEN 0 THEN 'Enabled' + WHEN 2 THEN 'Disabled' + ELSE battery_saver + END AS B_Saver, + timezone + FROM battery_event + ''') + + all_rows = cursor.fetchall() + db.close() + + if len(all_rows) == 0: + continue + + for row in all_rows: + timestamp = row[0] + if timestamp and timestamp != '': + timestamp = convert_utc_human_to_timezone(convert_ts_human_to_utc(timestamp), time_offset) + else: + timestamp = '' + data_list.append(( + timestamp, + row[1], + row[2], + row[3], + row[4], + source_file_turbo + )) + + data_headers = ( + ('Timestamp', 'datetime'), + 'Battery Level', + 'Charge Type', + 'Battery Saver', + 'Timezone', + 'Source' + ) + + return data_headers, data_list, source_file_turbo + @artifact_processor def Turbo_Bluetooth(files_found, report_folder, seeker, wrap_text): source_file_bluetooth = '' - turbo_db = '' data_list = [] + time_offset = 'UTC' + + for file_found in files_found: + file_found = str(file_found) - if file_found.lower().endswith('bluetooth.db'): - bluetooth_db = str(file_found) - source_file_bluetooth = file_found.replace(seeker.directory, '') - + # cek file yang benar + if not file_found.lower().endswith('bluetooth.db'): + continue + + bluetooth_db = file_found + source_file_bluetooth = os.path.basename(file_found) + db = open_sqlite_db_readonly(bluetooth_db) cursor = db.cursor() cursor.execute(''' select - datetime(timestamp_millis/1000,'unixepoch'), - bd_addr, - device_identifier, - battery_level, - volume_level, - time_zone + datetime(timestamp_millis/1000,'unixepoch'), + bd_addr, + device_identifier, + battery_level, + volume_level, + time_zone from battery_event - join device_address on battery_event.device_idx = device_address.device_idx + join device_address + on battery_event.device_idx = device_address.device_idx ''') all_rows = cursor.fetchall() - usageentries = len(all_rows) - if usageentries > 0: - for row in all_rows: - timestamp = row[0] - if timestamp is None: - pass - else: - timestamp = convert_utc_human_to_timezone(convert_ts_human_to_utc(timestamp),time_offset) - data_list.append((timestamp,row[1],row[2],row[3],row[4],row[5],file_found)) + + for row in all_rows: + timestamp = row[0] + if timestamp: + timestamp = convert_utc_human_to_timezone( + convert_ts_human_to_utc(timestamp), + time_offset + ) + + data_list.append(( + timestamp, + row[1], + row[2], + row[3], + row[4], + row[5], + file_found + )) + db.close() - data_headers = (('Timestamp','datetime'),'BT Device MAC Address','BT Device ID','Battery Level','Volume Level','Timezone','Source') + data_headers = ( + ('Timestamp','datetime'), + 'BT Device MAC Address', + 'BT Device ID', + 'Battery Level', + 'Volume Level', + 'Timezone', + 'Source' + ) - return data_headers, data_list, source_file_bluetooth \ No newline at end of file + return data_headers, data_list, source_file_bluetooth