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
3 changes: 2 additions & 1 deletion scripts/artifacts/gmailEmails.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def gmailEmails(files_found, report_folder, seeker, wrap_text):
attachname = row[15]
attachhash = row[16]
attachment = ''
messagehtml = ''

to = (message.get('1', '')).get('2', '') if '1' in message and '2' in message['1'] else '' #receiver
if isinstance(to, bytes):
Expand Down Expand Up @@ -257,4 +258,4 @@ def gmailDownloadRequests(files_found, report_folder, seeker, wrap_text):
data_list.append((record[0],record[1],record[2],record[3],record[4],record[5],record[6],record[7]))

data_headers = (('Timestamp Requested','datetime'),'Account Name','Download Type','Message ID','URL','Target File Path','Target File Size','Priority')
return data_headers, data_list, downloaderDB
return data_headers, data_list, downloaderDB
78 changes: 55 additions & 23 deletions scripts/artifacts/googleDuo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,48 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):

db = open_sqlite_db_readonly(file_found)
cursor = db.cursor()
cursor.execute('''
cursor.execute("PRAGMA table_info(activity_history);")
ah_cols = {col[1] for col in cursor.fetchall()}

has_other = 'other_id' in ah_cols
has_call_state = 'call_state' in ah_cols
has_outgoing = 'outgoing' in ah_cols

local_user_expr = "''"
remote_user_expr = "substr(other_id, 0,instr(other_id, '|'))" if has_other else "''"
call_status_expr = """case call_state
when 0 then 'Left Message'
when 1 then 'Missed Call'
when 2 then 'Answered'
when 4 then ''
end""" if has_call_state else "''"
direction_expr = """case outgoing
when 0 then 'Incoming'
when 1 then 'Outgoing'
end""" if has_outgoing else "''"
join_clause = "left join duo_users on duo_users.user_id = substr(other_id, 0,instr(other_id, '|'))" if has_other else ""

query = f'''
select
datetime(timestamp_usec/1000000, 'unixepoch') as 'Timestamp',
substr(self_id, 0,instr(self_id, '|')) as 'Local User',
substr(other_id, 0,instr(other_id, '|')) as 'Remote User',
{local_user_expr} as 'Local User',
{remote_user_expr} as 'Remote User',
duo_users.contact_display_name as 'Contact Name',
case activity_type
when 1 then 'Call'
when 2 then 'Note'
when 4 then 'Reaction'
end as 'Activity Type',
case call_state
when 0 then 'Left Message'
when 1 then 'Missed Call'
when 2 then 'Answered'
when 4 then ''
end as 'Call Status',
case outgoing
when 0 then 'Incoming'
when 1 then 'Outgoing'
end as 'Direction'
{call_status_expr} as 'Call Status',
{direction_expr} as 'Direction'
from activity_history
left join duo_users on duo_users.user_id = substr(other_id, 0,instr(other_id, '|'))
''')
{join_clause}
'''
try:
cursor.execute(query)
except sqlite3.OperationalError as e:
logfunc(f'Call history query failed in {file_found}: {e}')
continue

all_rows = cursor.fetchall()
usageentries = len(all_rows)
Expand Down Expand Up @@ -99,7 +117,19 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
else:
logfunc('No Google Duo - Contacts data available')

cursor.execute('''
cursor.execute("PRAGMA table_info(messages);")
msg_cols = {col[1] for col in cursor.fetchall()}

has_size = 'content_size_bytes' in msg_cols
has_saved = 'saved_status' in msg_cols

size_expr = 'content_size_bytes' if has_size else "''"
saved_expr = """case saved_status
when 0 then ''
when 1 then 'Yes'
end""" if has_saved else "''"

notes_query = f'''
select
case sent_timestamp_millis
when 0 then ''
Expand All @@ -117,13 +147,15 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
recipient_id,
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'
{size_expr} as 'Content Size',
{saved_expr} as 'File Saved'
from messages
''')
'''
try:
cursor.execute(notes_query)
except sqlite3.OperationalError as e:
logfunc(f'Google Duo notes query failed in {file_found}: {e}')
continue

all_rows = cursor.fetchall()
usageentries = len(all_rows)
Expand Down Expand Up @@ -174,4 +206,4 @@ def get_googleDuo(files_found, report_folder, seeker, wrap_text):
"Google Duo",
('*/com.google.android.apps.tachyon/databases/tachyon.db*','*/com.google.android.apps.tachyon/files/media/*.*'),
get_googleDuo)
}
}
32 changes: 21 additions & 11 deletions scripts/artifacts/googleMapsGmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,26 @@ def get_googleMapsGmm(files_found, report_folder, seeker, wrap_text):
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
''')
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='sync_item';")
if not cursor.fetchone():
logfunc(f'sync_item table not found in {file_found_myplaces}, skipping.')
db.close()
continue
try:
cursor.execute('''
select
rowid,
key_string,
round(latitude*.000001,6),
round(longitude*.000001,6),
sync_item,
timestamp
from sync_item
''')
except sqlite3.OperationalError as e:
logfunc(f'sync_item table query failed in {file_found_myplaces}: {e}')
db.close()
continue
all_rows = cursor.fetchall()

for row in all_rows:
Expand Down Expand Up @@ -150,4 +160,4 @@ def get_googleMapsGmm(files_found, report_folder, seeker, wrap_text):
tlactivity = f'Google Maps Label Places'
timeline(report_folder, tlactivity, data_list_myplaces, data_headers)
else:
logfunc('No Google Maps Label Places data available')
logfunc('No Google Maps Label Places data available')
31 changes: 25 additions & 6 deletions scripts/artifacts/googleMessages.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,44 @@ def get_googleMessages(files_found, report_folder, seeker, wrap_text):

db = open_sqlite_db_readonly(file_found)
cursor = db.cursor()
cursor.execute('''
cursor.execute("PRAGMA table_info(parts);")
parts_cols = {col[1] for col in cursor.fetchall()}

has_size = 'file_size_bytes' in parts_cols
has_cache = 'local_cache_path' in parts_cols
size_expr = 'parts.file_size_bytes' if has_size else "''"
cache_expr = 'parts.local_cache_path' if has_cache else (
'parts.storage_uri' if 'storage_uri' in parts_cols else (
'parts.uri' if 'uri' in parts_cols else "''"
)
)

query = f'''
SELECT
datetime(parts.timestamp/1000,'unixepoch') AS "Timestamp (UTC)",
parts.content_type AS "Message Type",
conversations.name AS "Other Participant/Conversation Name",
participants.display_destination AS "Message Sender",
parts.text AS "Message",
CASE
WHEN parts.file_size_bytes=-1 THEN "N/A"
ELSE parts.file_size_bytes
WHEN {size_expr}=-1 THEN "N/A"
ELSE {size_expr}
END AS "Attachment Byte Size",
parts.local_cache_path AS "Attachment Location"
{cache_expr} AS "Attachment Location"
FROM
parts
JOIN messages ON messages._id=parts.message_id
JOIN participants ON participants._id=messages.sender_id
JOIN conversations ON conversations._id=parts.conversation_id
ORDER BY "Timestamp (UTC)" ASC
''')
'''

try:
cursor.execute(query)
except sqlite3.OperationalError as e:
logfunc(f'Google Messages query failed in {file_found}: {e}')
db.close()
continue

all_rows = cursor.fetchall()
usageentries = len(all_rows)
Expand Down Expand Up @@ -69,4 +88,4 @@ def get_googleMessages(files_found, report_folder, seeker, wrap_text):
"Google Messages",
('*/com.google.android.apps.messaging/databases/bugle_db*'),
get_googleMessages)
}
}
13 changes: 9 additions & 4 deletions scripts/artifacts/notificationHistory.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def get_notificationHistory(files_found, report_folder, seeker, wrap_text):
value = setting.attrib.get('value')
value = "Enabled" if value == "1" else "Disabled" if value == "0" else "Unknown"
data_list.append((value, user))
else:
pass # setting not available
else:
pass # setting not available

if data_list:
description = f'Indicates whether "Notification History" feature is enabled.'
Expand All @@ -65,6 +65,8 @@ def get_notificationHistory(files_found, report_folder, seeker, wrap_text):
else:
logfunc('No Android Notification History - Status data available')

continue

#parsing notification_policy.xml
if file_name.endswith('notification_policy.xml'):
data_list = []
Expand Down Expand Up @@ -97,9 +99,11 @@ def get_notificationHistory(files_found, report_folder, seeker, wrap_text):

tsvname = f'Android Notification History - Snoozed notifications'
tsv(report_folder, data_headers, data_list, tsvname)

else:
logfunc('No Android Notification History - Snoozed notifications data available')

continue

else:
#iterate through the notification pbs
Expand All @@ -110,6 +114,7 @@ def get_notificationHistory(files_found, report_folder, seeker, wrap_text):
notification_history.ParseFromString(f.read()) #The error 'Wrong wire type in tag. ' likely happens due to the given .proto map file.
except Exception as e:
logfunc(f'Error in the ParseFromString() function. The error message was: {e}')
continue

package_map = {i + 1: pkg for i, pkg in enumerate(notification_history.string_pool.strings)} # one of the protobuf files stores the package name and indexes

Expand Down Expand Up @@ -179,4 +184,4 @@ def get_notificationHistory(files_found, report_folder, seeker, wrap_text):
tlactivity = f'Android Notification History - Notifications'
timeline(report_folder, tlactivity, data_pb_list, data_headers)
else:
logfunc(f'No Android Notification History - Notifications available')
logfunc(f'No Android Notification History - Notifications available')
61 changes: 33 additions & 28 deletions scripts/artifacts/wellbeing.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,34 +63,39 @@ def get_wellbeing(files_found, report_folder, seeker, wrap_text):
data_list.append((event_ts, row[2], row[3], file_found))

cursor = db.cursor()
cursor.execute('''
SELECT
datetime(component_events.timestamp/1000, "UNIXEPOCH") as timestamp,
component_events._id,
components.package_id,
packages.package_name,
components.component_name as website,
CASE
when component_events.type=1 THEN 'ACTIVITY_RESUMED'
when component_events.type=2 THEN 'ACTIVITY_PAUSED'
else component_events.type
END as eventType
FROM component_events
INNER JOIN components ON component_events.component_id=components._id
INNER JOIN packages ON components.package_id=packages._id
ORDER BY timestamp
''')
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='component_events';")
has_component_events = cursor.fetchone() is not None
if has_component_events:
cursor.execute('''
SELECT
datetime(component_events.timestamp/1000, "UNIXEPOCH") as timestamp,
component_events._id,
components.package_id,
packages.package_name,
components.component_name as website,
CASE
when component_events.type=1 THEN 'ACTIVITY_RESUMED'
when component_events.type=2 THEN 'ACTIVITY_PAUSED'
else component_events.type
END as eventType
FROM component_events
INNER JOIN components ON component_events.component_id=components._id
INNER JOIN packages ON components.package_id=packages._id
ORDER BY timestamp
''')

all_rows = cursor.fetchall()
usageentries = len(all_rows)
if usageentries > 0:
for row in all_rows:
event_ts = row[0]
if event_ts is None:
pass
else:
event_ts = convert_utc_human_to_timezone(convert_ts_human_to_utc(event_ts),'UTC')
data_list_url.append((event_ts, row[1], row[2], row[3], row[4], row[5], file_found))
all_rows = cursor.fetchall()
usageentries = len(all_rows)
if usageentries > 0:
for row in all_rows:
event_ts = row[0]
if event_ts is None:
pass
else:
event_ts = convert_utc_human_to_timezone(convert_ts_human_to_utc(event_ts),'UTC')
data_list_url.append((event_ts, row[1], row[2], row[3], row[4], row[5], file_found))
else:
logfunc('No component_events table in Digital Wellbeing database; skipping URL events.')
db.close()

else:
Expand Down Expand Up @@ -128,4 +133,4 @@ def get_wellbeing(files_found, report_folder, seeker, wrap_text):
tlactivity = f'Digital Wellbeing - URL Events'
timeline(report_folder, tlactivity, data_list, data_headers)
else:
logfunc('No Digital Wellbeing - URL Events data available')
logfunc('No Digital Wellbeing - URL Events data available')
Loading