Skip to content
Merged
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
26 changes: 24 additions & 2 deletions scripts/artifacts/discordChats.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,27 @@
"description": "Parses Discord chat messages from the kv-storage key-value store",
"author": "",
"creation_date": "2023-09-18",
"last_update_date": "2023-09-18",
"last_update_date": "2026-07-03",
"requirements": "none",
"category": "Discord Chats",
"notes": "",
"paths": ('*/data/com.discord/files/kv-storage/*/a*',),
"output_types": "standard",
"artifact_icon": "message-square",
"data_views": {
"conversation": {
"conversationDiscriminatorColumn": "Channel ID",
"textColumn": "Content",
"directionColumn": "Direction",
"directionSentValue": "Outgoing",
"timeColumn": "Timestamp",
"senderColumn": "Username"
}
},
}
}

import re
import sys
import json

Expand Down Expand Up @@ -42,6 +53,9 @@ def make_printable(s):
break # Skip all other files

source_path = str(file_found)
# local account id is part of the kv-storage path
account_match = re.search(r'@account\.(\d+)', source_path)
account_id = account_match.group(1) if account_match else ''
db = open_sqlite_db_readonly(file_found)
cursor = db.cursor()
cursor.execute('''
Expand All @@ -62,6 +76,7 @@ def make_printable(s):
channelid = (data['channelId'])
dataid = (data['id'])
username = (data['message']['author']['username'])
sender_id = str(data['message']['author'].get('id', ''))
content = (data['message']['content'])
attachments = (data['message']['attachments'])
if len(attachments) > 0:
Expand All @@ -76,7 +91,11 @@ def make_printable(s):
avatar = (data['message']['author']['avatar'])
editedtimestamp = (data['message']['edited_timestamp'])

data_list.append((datatimestamp,channelid,dataid,username,content,attachementfilename,attachementurl,attachmentproxyurl,mentions,mentionroles,pinned,avatar,editedtimestamp))
if account_id and sender_id:
direction = 'Outgoing' if sender_id == account_id else 'Incoming'
else:
direction = ''
data_list.append((datatimestamp,channelid,dataid,username,content,attachementfilename,attachementurl,attachmentproxyurl,mentions,mentionroles,pinned,avatar,editedtimestamp,sender_id,account_id,direction))

db.close()

Expand All @@ -94,5 +113,8 @@ def make_printable(s):
'Pinned',
'Avatar',
('Edited Timestamp', 'datetime'),
'Sender ID',
'Account ID',
'Direction',
)
return data_headers, data_list, source_path
33 changes: 29 additions & 4 deletions scripts/artifacts/googleChat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Google Chat messages (dynamite.db)",
"author": "Josh Hickman & Alexis Brignoni",
"creation_date": "2021-02-05",
"last_update_date": "2021-02-05",
"last_update_date": "2026-07-03",
"requirements": "blackboxprotobuf",
"category": "Google Chat",
"notes": "",
Expand All @@ -14,6 +14,17 @@
'*/com.google.android.apps.dynamite/databases/user_accounts/*/dynamite.db*'),
"output_types": "standard",
"artifact_icon": "message-square",
"data_views": {
"conversation": {
"conversationDiscriminatorColumn": "Group ID",
"conversationLabelColumn": "Group Name",
"textColumn": "Message",
"directionColumn": "Direction",
"directionSentValue": "Outgoing",
"timeColumn": "Message Timestamp",
"senderColumn": "Sender"
}
},
},
"get_googleChat_groups": {
"name": "Google Chat - Groups",
Expand Down Expand Up @@ -64,6 +75,7 @@

import datetime
import os
import re
import sqlite3

import blackboxprotobuf
Expand Down Expand Up @@ -139,19 +151,32 @@ def get_googleChat(files_found, report_folder, seeker, wrap_text):
for source_path in _dbs(files_found):
rows = _run(source_path, '''
SELECT topic_messages.create_time, Groups.name, users.name, topic_messages.text_body,
topic_messages.annotation
topic_messages.annotation, topic_messages.group_id, topic_messages.creator_id
FROM topic_messages
JOIN Groups on Groups.group_id=topic_messages.group_id
JOIN users ON users.user_id=topic_messages.creator_id
ORDER BY topic_messages.create_time ASC
''')
# the account email is in the db path (user_accounts/<email>/dynamite.db);
# resolve it to the gaia id via the users table of the same db
owner_id = ''
email_match = re.search(r'user_accounts/([^/]+)/dynamite\.db', source_path.replace('\\', '/'))
if email_match:
email_sql = email_match.group(1).replace("'", "''")
owner_rows = _run(source_path, f"SELECT user_id FROM users WHERE email = '{email_sql}'")
if owner_rows:
owner_id = str(owner_rows[0][0])
for r in rows:
ann = _parse_annotation(r[4])
data_list.append((_us_to_utc(r[0]), r[1], r[2], r[3]) + ann + (source_path,))
if owner_id and r[6] is not None:
direction = 'Outgoing' if str(r[6]) == owner_id else 'Incoming'
else:
direction = ''
data_list.append((_us_to_utc(r[0]), r[1], r[2], r[3]) + ann + (source_path, r[5], direction))

data_headers = (('Message Timestamp', 'datetime'), 'Group Name', 'Sender', 'Message',
'Meeting Code', 'Meeting URL', 'Meeting Sender', 'Meeting Sender Profile Pic URL',
'Filename', 'File Type', 'Width', 'Height', 'Source File')
'Filename', 'File Type', 'Width', 'Height', 'Source File', 'Group ID', 'Direction')
return data_headers, data_list, source_path


Expand Down
21 changes: 17 additions & 4 deletions scripts/artifacts/googleMessages.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@
"description": "Google Messages",
"author": "Josh Hickman (josh@thebinaryhick.blog)",
"creation_date": "2021-01-30",
"last_update_date": "2021-01-30",
"last_update_date": "2026-07-03",
"requirements": "None",
"category": "Google Messages",
"notes": "",
"paths": ('*/com.google.android.apps.messaging/databases/bugle_db*',),
"output_types": "standard",
"artifact_icon": "message-square",
"data_views": {
"conversation": {
"conversationDiscriminatorColumn": "Conversation ID",
"conversationLabelColumn": "Other Participant/Conversation Name",
"textColumn": "Message",
"directionColumn": "Direction",
"directionSentValue": "Outgoing",
"timeColumn": "Message Timestamp",
"senderColumn": "Message Sender"
}
},
}
}

Expand Down Expand Up @@ -44,7 +55,9 @@ def get_googleMessages(files_found, report_folder, seeker, wrap_text):
WHEN parts.file_size_bytes=-1 THEN "N/A"
ELSE parts.file_size_bytes
END AS "Attachment Byte Size",
parts.local_cache_path AS "Attachment Location"
parts.local_cache_path AS "Attachment Location",
parts.conversation_id AS "Conversation ID",
CASE WHEN participants.sub_id != -2 THEN 'Outgoing' ELSE 'Incoming' END AS "Direction"
FROM
parts
JOIN messages ON messages._id=parts.message_id
Expand All @@ -57,7 +70,7 @@ def get_googleMessages(files_found, report_folder, seeker, wrap_text):

for row in all_rows:
timestamp = datetime.datetime.fromtimestamp(int(row[0]) / 1000, datetime.timezone.utc) if row[0] else ''
data_list.append((timestamp, row[1], row[2], row[3], row[4], row[5], row[6]))
data_list.append((timestamp, row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]))

data_headers = (('Message Timestamp', 'datetime'), 'Message Type', 'Other Participant/Conversation Name', ('Message Sender', 'phonenumber'), 'Message', 'Attachment Byte Size', 'Attachment Location')
data_headers = (('Message Timestamp', 'datetime'), 'Message Type', 'Other Participant/Conversation Name', ('Message Sender', 'phonenumber'), 'Message', 'Attachment Byte Size', 'Attachment Location', 'Conversation ID', 'Direction')
return data_headers, data_list, source_path
29 changes: 25 additions & 4 deletions scripts/artifacts/tikTok.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@
"description": "",
"author": "",
"creation_date": "2021-03-02",
"last_update_date": "2021-03-02",
"last_update_date": "2026-07-03",
"requirements": "none",
"category": "TikTok",
"notes": "",
"paths": ('*_im.db*', '*db_im_xx*'),
"output_types": "standard",
"artifact_icon": "message-square",
"data_views": {
"conversation": {
"conversationDiscriminatorColumn": "Conversation ID",
"textColumn": "Message",
"directionColumn": "Direction",
"directionSentValue": "Outgoing",
"timeColumn": "Timestamp",
"senderColumn": "Nickname"
}
},
},
"get_tikTok_contacts": {
"name": "TikTok - Contacts",
Expand All @@ -29,6 +39,8 @@
}

import datetime
import os
import re

from scripts.ilapfuncs import artifact_processor, open_sqlite_db_readonly

Expand Down Expand Up @@ -66,19 +78,28 @@ def get_tikTok(files_found, report_folder, seeker, wrap_text):
case when read_status = 0 then 'Not read'
when read_status = 1 then 'Read'
else read_status
end local_info
end local_info,
conversation_id
from db_im_xx.SIMPLE_USER, msg
where UID = sender and json_valid(content) = 1 order by created_time
''')
all_rows = cursor.fetchall()
db.close()

# local account uid is the filename prefix: <uid>_im.db
uid_match = re.search(r'(\d+)_im\.db$', os.path.basename(maindb))
account_uid = uid_match.group(1) if uid_match else ''
for row in all_rows:
timestamp = datetime.datetime.fromtimestamp(int(row[0]) / 1000, datetime.timezone.utc) if row[0] else ''
data_list.append((timestamp, row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]))
if account_uid and row[1] is not None:
direction = 'Outgoing' if str(row[1]) == account_uid else 'Incoming'
else:
direction = ''
data_list.append((timestamp, row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8],
row[9], direction))

data_headers = (('Timestamp', 'datetime'), 'UID', 'Unique ID', 'Nickname', 'Message', 'Link GIF Name',
'Link GIF URL', 'Read?', 'Local Info')
'Link GIF URL', 'Read?', 'Local Info', 'Conversation ID', 'Direction')
return data_headers, data_list, source_path


Expand Down
Loading