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
36 changes: 17 additions & 19 deletions scripts/artifacts/LinkedIn.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
# LinkedIn App (com.linkedin.android)
# Author: Marco Neumann (kalinko@be-binary.de)
# Version: 0.0.1
#
# Tested with the following versions:
# 2024-08-16: Android 14, App: 4.1966
# 2024-08-16: Android 14, App: 4.1.966
# 2025-02-07: Android 16, App: 4.1.1166

# Requirements: json, xml


__artifacts_v2__ = {


"get_linkedin_account": {
"linkedin_account": {
"name": "LinkedIn - Account",
"description": "Existing account in LinkedIn App. The Public Identifier can be used to visit the public profile on the LinkedIn Website (https://www.linkedin.com/in/[Public Identifier]).",
"author": "Marco Neumann {kalinko@be-binary.de}",
"version": "0.0.1",
"date": "2025-04-26",
"requirements": "xml",
"version": "0.1",
"creation_date": "2025-04-26",
'last_update_date': '2026-02-07',
"requirements": "xml, json",
"category": "LinkedIn",
"notes": "",
"paths": ('*/com.linkedin.android/shared_prefs/linkedInPrefsName.xml'),
"output_types": "standard",
"artifact_icon": "user"
},
"get_linkedin_messages": {
"linkedin_messages": {
"name": "LinkedIn - Messages",
"description": "Messages sent and received from LinkedIn App",
"author": "Marco Neumann {kalinko@be-binary.de}",
"version": "0.0.1",
"date": "2025-04-26",
"version": "0.1",
'creation_date': '2025-04-26',
'last_update_date': '2026-02-07',
"requirements": "",
"category": "LinkedIn",
"notes": "",
Expand All @@ -39,14 +38,13 @@
}
}


import json
import xml.etree.ElementTree as ET

from scripts.ilapfuncs import artifact_processor, convert_unix_ts_to_utc, get_sqlite_db_records

@artifact_processor
def get_linkedin_account(files_found, report_folder, seeker, wrap_text):
def linkedin_account(files_found, _report_folder, _seeker, _wrap_text):

# Get data from xml into a dict to work with
xml_dict = {}
Expand Down Expand Up @@ -74,21 +72,21 @@ def get_linkedin_account(files_found, report_folder, seeker, wrap_text):
first_name = temp_meModel['miniProfile']['firstName']
headline = temp_meModel['miniProfile']['occupation']
public_identifier = temp_meModel['miniProfile']['publicIdentifier']
data_list = [(last_login, member_id, last_name, first_name, headline, public_identifier)]
data_list = [(last_login, member_id, account_mail, last_name, first_name, headline, public_identifier)]

data_headers = ('Last Login', 'Member ID', 'Last Name', 'First Name', 'Headline', 'Public Identifier')
data_headers = ('Last Login', 'Member ID', 'Account Mail', 'Last Name', 'First Name', 'Headline', 'Public Identifier')

return data_headers, data_list, files_found[0]



@artifact_processor
def get_linkedin_messages(files_found, report_folder, seeker, wrap_text):
def linkedin_messages(files_found, _report_folder, _seeker, _wrap_text):
files_found = [x for x in files_found if not x.endswith('wal') and not x.endswith('shm')]

query = ('''
SELECT
strftime('%Y-%m-%d %H:%M:%S.', "md"."deliveredAt"/1000, 'unixepoch') || ("md"."deliveredAt"%1000) [deliveredAt],
md.deliveredAt[deliveredAt],
CASE WHEN md.status = '5'
THEN 'Delivered'
ELSE 'Unknown'
Expand All @@ -109,7 +107,7 @@ def get_linkedin_messages(files_found, report_folder, seeker, wrap_text):

data_list = []
for row in db_records:
delivery_date = row[0]
delivery_date = convert_unix_ts_to_utc(int(row[0])/1000)
delivery_status = row[1]
sender_firstname = row[2]
sender_lastname = row[3]
Expand Down
228 changes: 228 additions & 0 deletions scripts/artifacts/RandoChat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# Android RandoChat App (com.random.chat.app)

# Tested Version: 6.3.3


__artifacts_v2__ = {
'randochat_messages': {
'name': 'RandoChat Messages',
'description': 'Parses RandoChat App Messages',
'author': 'Marco Neumann {kalinko@be-binary.de}',
'version': '0.0.1',
'creation_date': '2026-01-15',
'last_update_date': '2026-01-15',
'requirements': 'os, path',
'category': 'Chats',
'notes': '',
'paths': (
'*/data/com.random.chat.app/databases/ramdochatV2.db*',
'*/Android/data/com.random.chat.app/files/Pictures/RandoChat/*',
'*/Android/data/com.random.chat.app/files/images/*',
'*/Android/data/com.random.chat.app/files/Music/RandoChat/*'
),
'output_types': 'standard',
'artifact_icon': 'message-square',
"html_columns": ["Media File"]
},
'randochat_account': {
'name': 'RandoChat Accounts',
'description': 'Parses RandoChat App Accounts',
'author': 'Marco Neumann {kalinko@be-binary.de}',
'version': '0.0.1',
'creation_date': '2026-01-15',
'last_update_date': '2026-01-15',
'requirements': '',
'category': 'Accounts',
'notes': '',
'paths': (
'*/data/com.random.chat.app/databases/ramdochatV2.db*'
),
'output_types': 'standard',
'artifact_icon': 'user'
},
'randochat_contacts': {
'name': 'RandoChat Contacts',
'description': 'Parses RandoChat App Contacts',
'author': 'Marco Neumann {kalinko@be-binary.de}',
'version': '0.0.1',
'creation_date': '2026-01-15',
'last_update_date': '2026-01-15',
'requirements': '',
'category': 'Contacts',
'notes': '',
'paths': (
'*/data/com.random.chat.app/databases/ramdochatV2.db*'
),
'output_types': 'standard',
'artifact_icon': 'user'
}
}

import os

from scripts.ilapfuncs import artifact_processor, convert_unix_ts_to_utc, get_sqlite_db_records, media_to_html

@artifact_processor
def randochat_messages(files_found, report_folder, _seeker, _wrap_text):
files_found = [x for x in files_found if not x.endswith('wal') and not x.endswith('shm')]


# Get the different files found and store their pathes in corresponding lists to work with them
main_db = ''
attachments = []

for file_found in files_found:
file_found = str(file_found)

if file_found.endswith('ramdochatV2.db'):
main_db = file_found

if 'files' in os.path.dirname(file_found):
attachments.append(file_found)


query = '''
SELECT
m.hora [Timestamp],
m.mensagem [Message Content],
c.apelido [Contact Username],
m.minha [Sent?], -- 1 = sent, 2 = received
m.url [Media File],
m.id_talk_server [Conversation ID],
m.id_servidor [Message ID]
FROM mensagens m
LEFT JOIN conversa c ON c.id_server = m.id_talk_server
'''

db_records = get_sqlite_db_records(main_db, query)
data_list = []

for row in db_records:
timestamp = convert_unix_ts_to_utc(int(row[0])/1000)
content = row[1]
contact_name = row[2]
direction = row[3]
media_file = row[4]
conv_id = row[5]
message_id = row[6]

# Handling attachments
if media_file is None:
attachment = 0
else:
attachment = ''
for att_path in attachments:
if os.path.basename(media_file) in os.path.basename(att_path):
attachment = media_to_html(os.path.basename(att_path), attachments, report_folder)

data_list.append((timestamp, content, contact_name, direction, attachment, conv_id, message_id))

data_headers = ('Timestamp', 'Content', 'Contact Username', 'Sent?', 'Media File', 'Conversation ID', 'Message ID')

return data_headers, data_list, main_db


@artifact_processor
def randochat_account(files_found, _report_folder, _seeker, _wrap_text):
files_found = [x for x in files_found if not x.endswith('wal') and not x.endswith('shm')]


# Get the different files found and store their pathes in corresponding lists to work with them
main_db = ''

for file_found in files_found:
file_found = str(file_found)

if file_found.endswith('ramdochatV2.db'):
main_db = file_found


query = '''
SELECT
MAX(CASE WHEN name LIKE 'apelido' THEN value END) [Username],
MAX(CASE WHEN name LIKE 'sexo' THEN (CASE WHEN value = 'H' THEN 'Male' WHEN value = 'M' THEN 'Female' END) END) [User Sex],
MAX(CASE WHEN name LIKE 'idade' THEN value END) [User Age],
MAX(CASE WHEN name LIKE 'language' THEN value END) [Language],
MAX(CASE WHEN name LIKE 'device_id' THEN value END) [Device ID],
MAX(CASE WHEN name LIKE 'idade_de' THEN value END) [Preferred Age From],
MAX(CASE WHEN name LIKE 'idade_ate' THEN value END) [Preferred Age To],
MAX(CASE WHEN name LIKE 'sexo_search' THEN (CASE WHEN value = 'H' THEN 'Male' WHEN value = 'M' THEN 'Female' END) END) [Preferred Sex]
FROM configuracao
'''

db_records = get_sqlite_db_records(main_db, query)
data_list = []

for row in db_records:
username = row[0]
sex = row[1]
age = row[2]
language = row[3]
device_id = row[4]
age_from = row[5]
age_to = row[6]
preferred_sex = row[7]


data_list.append((username, sex, age, language, device_id, age_from, age_to, preferred_sex))

data_headers = ('Username', 'User Sex', 'User Age', 'Language', 'Device ID', 'Preferred Age From', 'Preferred Age To', 'Preferred Sex')

return data_headers, data_list, main_db


@artifact_processor
def randochat_contacts(files_found, _report_folder, _seeker, _wrap_text):
files_found = [x for x in files_found if not x.endswith('wal') and not x.endswith('shm')]
main_db = ''

for file_found in files_found:
file_found = str(file_found)

if file_found.endswith('ramdochatV2.db'):
main_db = file_found


query = '''
SELECT
c.id_pessoa [Account ID],
c.apelido [Username],
c.idade [Age],
CASE
WHEN c.sexo = 'M' THEN 'Female' -- From Mulher
WHEN c.sexo = 'H' THEN 'Male' -- From Homem
END [Sex],
CASE
WHEN c.favorite = 1 THEN 'Yes'
WHEN c.favorite = 0 THEN 'No'
END [Favorite?],
CASE
WHEN c.bloqueado = 1 THEN 'Yes'
WHEN c.bloqueado = 0 THEN 'No'
END [Blocked?],
CASE WHEN
c.images = '' THEN 'n/a'
ELSE
json_extract(c.images, "$[0].img")
END [Link Profile Pic]
FROM conversa c
'''

db_records = get_sqlite_db_records(main_db, query)
data_list = []

for row in db_records:
account_id = row[0]
username = row[1]
age = row[2]
sex = row[3]
favorite = row[4]
blocked = row[5]
profile_pic = row[6]


data_list.append((account_id, username, age, sex, favorite, blocked, profile_pic))

data_headers = ('Account ID', 'Username', 'Age', 'Sex', 'Favorite?', 'Blocked?', 'Link Profile Pic')

return data_headers, data_list, main_db
Loading