Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions scripts/artifacts/FacebookMessenger.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,17 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text):
source_file = file_found.replace(seeker.data_folder, '')
db = open_sqlite_db_readonly(file_found)
cursor = db.cursor()

cursor.execute("PRAGMA table_info('messages');")
cols = [r[1] for r in cursor.fetchall()]
if 'reaction_timestamp' in cols:
reaction_ts_sql = 'datetime(message_reactions.reaction_timestamp/1000,\'unixepoch\') as "Message Reaction Timestamp",'
else:
reaction_ts_sql = "'' as \"Message Reaction Timestamp\","


try:
cursor.execute('''
cursor.execute(f'''
select
case messages.timestamp_ms
when 0 then ''
Expand All @@ -224,7 +233,7 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text):
(select json_extract (messages.shares, '$[0].description')) as ShareDesc,
(select json_extract (messages.shares, '$[0].href')) as ShareLink,
message_reactions.reaction as "Message Reaction",
datetime(message_reactions.reaction_timestamp/1000,'unixepoch') as "Message Reaction Timestamp",
{reaction_ts_sql}
messages.msg_id
from messages, threads
left join message_reactions on message_reactions.msg_id = messages.msg_id
Expand All @@ -233,7 +242,7 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text):
''')
snippet = 1
except:
cursor.execute('''
cursor.execute(f'''
select
case messages.timestamp_ms
when 0 then ''
Expand All @@ -248,7 +257,7 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text):
(select json_extract (messages.shares, '$[0].description')) as ShareDesc,
(select json_extract (messages.shares, '$[0].href')) as ShareLink,
message_reactions.reaction as "Message Reaction",
datetime(message_reactions.reaction_timestamp/1000,'unixepoch') as "Message Reaction Timestamp",
{reaction_ts_sql}
messages.msg_id
from messages, threads
left join message_reactions on message_reactions.msg_id = messages.msg_id
Expand Down Expand Up @@ -322,8 +331,26 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text):
timeline(report_folder, tlactivity, data_list, data_headers)
else:
logfunc(f'No Facebook{typeof}- Calls{usernum} - threads_db2 data available')

cursor.execute("PRAGMA table_info('thread_users');")
cols = [r[1] for r in cursor.fetchall()]
if 'friendship_status' in cols:
friendship_status_sql = """case friendship_status
when 0 then "N/A (Self)"
when 1 then "Friends"
when 2 then "Friend Request Received"
when 3 then "Friend Request Sent"
when 4 then "Not Friends"
end as "Friendship Status","""
else:
friendship_status_sql = "'' as \"Friendship Status\","

if 'contact_relationship_status' in cols:
contact_relationship_status_sql = 'contact_relationship_status as "Contact Relationship Status"'
else:
contact_relationship_status_sql = "'' as \"Contact Relationship Status\""

cursor.execute('''
cursor.execute(f'''
select
substr(user_key,10),
first_name,
Expand All @@ -338,8 +365,8 @@ def get_FacebookMessenger(files_found, report_folder, seeker, wrap_text):
when 0 then 'No'
when 1 then 'Yes'
end is_friend,
friendship_status,
contact_relationship_status
{friendship_status_sql}
{contact_relationship_status_sql}
from thread_users
''')

Expand Down
96 changes: 60 additions & 36 deletions scripts/artifacts/deviceHealthServices_Battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,26 @@
"artifact_icon": "bluetooth"
}
}

import sqlite3
import textwrap
import os

from packaging import version
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

@artifact_processor
def Turbo_Battery(files_found, report_folder, seeker, wrap_text):
def Turbo_Battery(files_found, _report_folder, seeker, _wrap_text):
source_file_turbo = ''
turbo_db = ''
data_list = []

# try to get a timezone offset from seeker; fall back to UTC (0) if not available
try:
time_offset = getattr(seeker, 'timezone', None)
if time_offset is None:
time_offset = getattr(seeker, 'time_zone', 0)
if time_offset is None:
time_offset = 0
except AttributeError:
time_offset = 0

for file_found in files_found:
file_found = str(file_found)
if file_found.lower().endswith('turbo.db'):
Expand Down Expand Up @@ -80,7 +85,11 @@ def Turbo_Battery(files_found, report_folder, seeker, wrap_text):
if timestamp is None:
pass
else:
timestamp = convert_utc_human_to_timezone(convert_ts_human_to_utc(timestamp),time_offset)
try:
timestamp = convert_utc_human_to_timezone(convert_ts_human_to_utc(timestamp), time_offset)
except (ValueError, TypeError):
# fallback to UTC human timestamp if timezone conversion fails
timestamp = convert_ts_human_to_utc(timestamp)
data_list.append((timestamp,row[1],row[2],row[3],row[4],file_found))

db.close()
Expand All @@ -90,40 +99,55 @@ def Turbo_Battery(files_found, report_folder, seeker, wrap_text):
return data_headers, data_list, source_file_turbo

@artifact_processor
def Turbo_Bluetooth(files_found, report_folder, seeker, wrap_text):
def Turbo_Bluetooth(files_found, _report_folder, seeker, _wrap_text):
source_file_bluetooth = ''
turbo_db = ''
bluetooth_db = ''
data_list = []

if file_found.lower().endswith('bluetooth.db'):
bluetooth_db = str(file_found)
source_file_bluetooth = file_found.replace(seeker.directory, '')
# attempt to get timezone offset; default to UTC (0)
try:
time_offset = getattr(seeker, 'timezone', None)
if time_offset is None:
time_offset = getattr(seeker, 'time_zone', 0)
if time_offset is None:
time_offset = 0
except AttributeError:
time_offset = 0

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, '')

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
from battery_event
join device_address on battery_event.device_idx = device_address.device_idx
''')
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
from battery_event
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))
db.close()
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:
try:
timestamp = convert_utc_human_to_timezone(convert_ts_human_to_utc(timestamp), time_offset)
except (ValueError, TypeError):
timestamp = convert_ts_human_to_utc(timestamp)
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')

Expand Down
64 changes: 43 additions & 21 deletions scripts/artifacts/googleDuo.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import os
import shutil
import sqlite3
import textwrap
import scripts.artifacts.artGlobals

from scripts.artifact_report import ArtifactHtmlReport
from scripts.ilapfuncs import logfunc, tsv, timeline, is_platform_windows, open_sqlite_db_readonly
from scripts.ilapfuncs import logfunc, tsv, timeline, open_sqlite_db_readonly

def get_googleDuo(files_found, report_folder, seeker, wrap_text):
def get_googleDuo(files_found, report_folder, _seeker, _wrap_text):

for file_found in files_found:
file_found = str(file_found)
Expand All @@ -16,6 +14,8 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):

db = open_sqlite_db_readonly(file_found)
cursor = db.cursor()

# --- Query 1: Activity History ---
cursor.execute('''
select
datetime(timestamp_usec/1000000, 'unixepoch') as 'Timestamp',
Expand Down Expand Up @@ -47,22 +47,23 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
report = ArtifactHtmlReport('Google Duo - Call History')
report.start_artifact_report(report_folder, 'Google Duo - Call History')
report.add_script()
data_headers = ('Timestamp','Local User','Remote User','Contact Name','Activity Type','Call Status','Direction') # Don't remove the comma, that is required to make this a tuple as there is only 1 element
data_headers = ('Timestamp','Local User','Remote User','Contact Name','Activity Type','Call Status','Direction')
data_list = []
for row in all_rows:
data_list.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6]))

report.write_artifact_data_table(data_headers, data_list, file_found)
report.end_artifact_report()

tsvname = f'Google Duo - Call History'
tsvname = 'Google Duo - Call History'
tsv(report_folder, data_headers, data_list, tsvname)

tlactivity = f'Google Duo - Call History'
tlactivity = 'Google Duo - Call History'
timeline(report_folder, tlactivity, data_list, data_headers)
else:
logfunc('No Google Duo - Call History data available')

# --- Query 2: Contacts ---
cursor.execute('''
select
case system_contact_last_update_millis
Expand All @@ -83,23 +84,44 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
report = ArtifactHtmlReport('Google Duo - Contacts')
report.start_artifact_report(report_folder, 'Google Duo - Contacts')
report.add_script()
data_headers = ('Last Updated Timestamp','Contact Name','Contact Info','Contact Label','Contact ID') # Don't remove the comma, that is required to make this a tuple as there is only 1 element
data_headers = ('Last Updated Timestamp','Contact Name','Contact Info','Contact Label','Contact ID')
data_list = []
for row in all_rows:
data_list.append((row[0],row[1],row[2],row[3],row[4]))

report.write_artifact_data_table(data_headers, data_list, file_found)
report.end_artifact_report()

tsvname = f'Google Duo - Contacts'
tsvname = 'Google Duo - Contacts'
tsv(report_folder, data_headers, data_list, tsvname)

tlactivity = f'Google Duo - Contacts'
tlactivity = 'Google Duo - Contacts'
timeline(report_folder, tlactivity, data_list, data_headers)
else:
logfunc('No Google Duo - Contacts data available')

cursor.execute('''
# --- Query 3: Messages / Notes (FIXED) ---

# Check if 'saved_status' column exists in 'messages' table
try:
cursor.execute("PRAGMA table_info(messages)")
columns = [column[1] for column in cursor.fetchall()]
has_saved_status = 'saved_status' in columns
except (sqlite3.OperationalError, sqlite3.DatabaseError):
has_saved_status = False

# Define the 'File Saved' column selection based on availability
if has_saved_status:
saved_status_query = """
case saved_status
when 0 then ''
when 1 then 'Yes'
end as 'File Saved'
"""
else:
saved_status_query = "'' as 'File Saved'"

query_messages = f'''
select
case sent_timestamp_millis
when 0 then ''
Expand All @@ -118,12 +140,11 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
content_uri,
replace(content_uri, rtrim(content_uri, replace(content_uri, '/', '')), '') as 'File Name',
content_size_bytes,
case saved_status
when 0 then ''
when 1 then 'Yes'
end as 'File Saved'
{saved_status_query}
from messages
''')
'''

cursor.execute(query_messages)

all_rows = cursor.fetchall()
usageentries = len(all_rows)
Expand All @@ -137,7 +158,7 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
viewed_ts = row[2]
sender_id = row[3]
recipient_id = row[4]
content_uri = row[5]
# row[5] is content_uri, unused in report logic, removed to fix pylint warning
content_name = row[6]
content_size = row[7]
file_saved = row[8]
Expand All @@ -149,20 +170,21 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
data_file_name = os.path.basename(match)
thumb = f'<img src="{report_folder}/{data_file_name}" width="300"></img>'

data_list.append((row[0],row[1],row[2],row[3],row[4],thumb,row[7],row[8]))
# FIX: Using the variables defined above instead of row[] indices
data_list.append((sent_ts, received_ts, viewed_ts, sender_id, recipient_id, thumb, content_size, file_saved))

report = ArtifactHtmlReport('Google Duo - Notes')
report.start_artifact_report(report_folder, 'Google Duo - Notes')
report.add_script()
data_headers = ('Sent Timestamp','Received Timestamp','Viewed Timestamp','Sender','Recipient','Content','Size','File Saved') # Don't remove the comma, that is required to make this a tuple as there is only 1 element
data_headers = ('Sent Timestamp','Received Timestamp','Viewed Timestamp','Sender','Recipient','Content','Size','File Saved')

report.write_artifact_data_table(data_headers, data_list, file_found, html_no_escape=['Content'])
report.end_artifact_report()

tsvname = f'Google Duo - Notes'
tsvname = 'Google Duo - Notes'
tsv(report_folder, data_headers, data_list, tsvname)

tlactivity = f'Google Duo - Notes'
tlactivity = 'Google Duo - Notes'
timeline(report_folder, tlactivity, data_list, data_headers)
else:
logfunc('No Google Duo - Notes data available')
Expand Down
Loading