diff --git a/admin/test/cases/data/teams/testdata.teams.teamsCalls.josh_ios15_ffs.zip b/admin/test/cases/data/teams/testdata.teams.teamsCalls.josh_ios15_ffs.zip new file mode 100644 index 00000000..3638dc4d Binary files /dev/null and b/admin/test/cases/data/teams/testdata.teams.teamsCalls.josh_ios15_ffs.zip differ diff --git a/admin/test/cases/data/teams/testdata.teams.teamsContacts.josh_ios15_ffs.zip b/admin/test/cases/data/teams/testdata.teams.teamsContacts.josh_ios15_ffs.zip new file mode 100644 index 00000000..58b1471a Binary files /dev/null and b/admin/test/cases/data/teams/testdata.teams.teamsContacts.josh_ios15_ffs.zip differ diff --git a/admin/test/cases/data/teams/testdata.teams.teamsLocations.josh_ios15_ffs.zip b/admin/test/cases/data/teams/testdata.teams.teamsLocations.josh_ios15_ffs.zip new file mode 100644 index 00000000..5ff61af2 Binary files /dev/null and b/admin/test/cases/data/teams/testdata.teams.teamsLocations.josh_ios15_ffs.zip differ diff --git a/admin/test/cases/data/teams/testdata.teams.teamsMessages.josh_ios15_ffs.zip b/admin/test/cases/data/teams/testdata.teams.teamsMessages.josh_ios15_ffs.zip new file mode 100644 index 00000000..a6eb957b Binary files /dev/null and b/admin/test/cases/data/teams/testdata.teams.teamsMessages.josh_ios15_ffs.zip differ diff --git a/admin/test/cases/data/teams/testdata.teams.teamsUser.josh_ios15_ffs.zip b/admin/test/cases/data/teams/testdata.teams.teamsUser.josh_ios15_ffs.zip new file mode 100644 index 00000000..91bdc86e Binary files /dev/null and b/admin/test/cases/data/teams/testdata.teams.teamsUser.josh_ios15_ffs.zip differ diff --git a/admin/test/cases/testdata.teams.json b/admin/test/cases/testdata.teams.json new file mode 100644 index 00000000..44289763 --- /dev/null +++ b/admin/test/cases/testdata.teams.json @@ -0,0 +1,72 @@ +{ + "josh_ios15_ffs": { + "description": "", + "maker": "", + "make_data": { + "input_data_path": "/Users/jameshabben/Documents/phone-images/Josh/iOS_15_Public_Image.tar.gz", + "os": "macOS-15.0-x86_64-i386-64bit", + "timestamp": "2024-11-04T13:38:37.697695", + "last_commit": { + "hash": "de6fdbe64b57ef6f08cdcc5947c2bd89a6eb677c", + "author_name": "abrignoni", + "author_email": "abrignoni@gmail.com", + "date": "2023-10-10T21:56:23-04:00", + "message": "Timezone offset main code" + } + }, + "artifacts": { + "teamsMessages": { + "search_patterns": [ + "*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*", + "*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/Downloads/*/Images/*" + ], + "file_count": 34, + "expected_output": { + "headers": [], + "data": [] + } + }, + "teamsContacts": { + "search_patterns": [ + "*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*" + ], + "file_count": 1, + "expected_output": { + "headers": [], + "data": [] + } + }, + "teamsUser": { + "search_patterns": [ + "*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*" + ], + "file_count": 1, + "expected_output": { + "headers": [], + "data": [] + } + }, + "teamsCalls": { + "search_patterns": [ + "*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*" + ], + "file_count": 1, + "expected_output": { + "headers": [], + "data": [] + } + }, + "teamsLocations": { + "search_patterns": [ + "*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*" + ], + "file_count": 1, + "expected_output": { + "headers": [], + "data": [] + } + } + }, + "image_name": "josh_ios15_ffs" + } +} \ No newline at end of file diff --git a/admin/test/results/teams/teams.teamsCalls.josh_ios15_ffs.20241104220314.json b/admin/test/results/teams/teams.teamsCalls.josh_ios15_ffs.20241104220314.json new file mode 100644 index 00000000..69dea678 --- /dev/null +++ b/admin/test/results/teams/teams.teamsCalls.josh_ios15_ffs.20241104220314.json @@ -0,0 +1,120 @@ +{ + "metadata": { + "module_name": "teams", + "artifact_name": "Teams Call Logs", + "function_name": "teamsCalls", + "case_number": "josh_ios15_ffs", + "number_of_columns": 14, + "number_of_rows": 5, + "total_data_size_bytes": 1076, + "input_zip_path": "admin/test/cases/data/teams/testdata.teams.teamsCalls.josh_ios15_ffs.zip", + "start_time": "2024-11-04T22:03:14.018178+00:00", + "end_time": "2024-11-04T22:03:14.110998+00:00", + "run_time_seconds": 0.014806032180786133, + "last_commit": { + "hash": "de6fdbe64b57ef6f08cdcc5947c2bd89a6eb677c", + "author_name": "abrignoni", + "author_email": "abrignoni@gmail.com", + "date": "2023-10-10T21:56:23-04:00", + "message": "Timezone offset main code" + } + }, + "headers": [ + "Compose Timestamp", + "From", + "Display Name", + "Content", + "Call Start", + "Call Connect", + "Call End", + "Call Direction", + "Call Type", + "Call State", + "Call Originator", + "Call Target", + "Call Originator Name", + "Call Participant Name" + ], + "data": [ + [ + 1682691710, + "8:live:.cid.832804f1252ed9b2", + "", + "Call Logs for Call 6d8fa7a2-9a2e-4cf2-8c87-cc946fbe5663", + 1682691683, + 1682691687, + 1682691710, + "outgoing", + "twoParty", + "accepted", + "8:live:.cid.832804f1252ed9b2", + "8:live:.cid.5638f9306b7b8594", + "ThisIs DFIR", + "" + ], + [ + 1682691942, + "8:live:.cid.832804f1252ed9b2", + "", + "Call Logs for Call 1019490c-938a-45be-a3ec-b10069e7c2fc", + 1682691917, + 1682691921, + 1682691942, + "outgoing", + "twoParty", + "accepted", + "8:live:.cid.832804f1252ed9b2", + "8:live:.cid.5638f9306b7b8594", + "ThisIs DFIR", + "" + ], + [ + 1682692211, + "8:live:.cid.832804f1252ed9b2", + "", + "Call Logs for Call 7f99d235-02b8-4c1c-adb7-f94038019166", + 1682692116, + 1682692120, + 1682692211, + "incoming", + "twoParty", + "accepted", + "8:live:.cid.5638f9306b7b8594", + "8:live:.cid.832804f1252ed9b2", + "ThisIs DfirTwo", + "" + ], + [ + 1682692733, + "8:live:.cid.832804f1252ed9b2", + "", + "Call Logs for Call c808711b-e466-4eaf-86c2-2eae9b1cc39b", + 1682692633, + 1682692636, + 1682692732, + "outgoing", + "twoParty", + "accepted", + "8:live:.cid.832804f1252ed9b2", + "8:live:.cid.5638f9306b7b8594", + "ThisIs DFIR", + "" + ], + [ + 1682693144, + "8:live:.cid.832804f1252ed9b2", + "", + "Call Logs for Call 43f78ea2-b67f-4e73-af63-eeeabab8ed68", + 1682693035, + 1682693039, + 1682693144, + "incoming", + "twoParty", + "accepted", + "8:live:.cid.5638f9306b7b8594", + "8:live:.cid.832804f1252ed9b2", + "ThisIs DfirTwo", + "" + ] + ] +} \ No newline at end of file diff --git a/admin/test/results/teams/teams.teamsContacts.josh_ios15_ffs.20241104220314.json b/admin/test/results/teams/teams.teamsContacts.josh_ios15_ffs.20241104220314.json new file mode 100644 index 00000000..5d2f7e60 --- /dev/null +++ b/admin/test/results/teams/teams.teamsContacts.josh_ios15_ffs.20241104220314.json @@ -0,0 +1,74 @@ +{ + "metadata": { + "module_name": "teams", + "artifact_name": "Teams Contacts", + "function_name": "teamsContacts", + "case_number": "josh_ios15_ffs", + "number_of_columns": 3, + "number_of_rows": 9, + "total_data_size_bytes": 244, + "input_zip_path": "admin/test/cases/data/teams/testdata.teams.teamsContacts.josh_ios15_ffs.zip", + "start_time": "2024-11-04T22:03:14.111761+00:00", + "end_time": "2024-11-04T22:03:14.186558+00:00", + "run_time_seconds": 0.0014848709106445312, + "last_commit": { + "hash": "de6fdbe64b57ef6f08cdcc5947c2bd89a6eb677c", + "author_name": "abrignoni", + "author_email": "abrignoni@gmail.com", + "date": "2023-10-10T21:56:23-04:00", + "message": "Timezone offset main code" + } + }, + "headers": [ + "Display Name", + "Email", + "Phone Number" + ], + "data": [ + [ + "ThisIs DFIRThree", + "thisisdfirthree@gmail.com", + "" + ], + [ + "This Is DFIR Two", + "", + "9198887386" + ], + [ + "This Is DFIR Two", + "thisisdfirtwo@gmail.com", + "" + ], + [ + "Josh Hickman", + "", + "9195790479" + ], + [ + "Josh Hickman", + "", + "9193912507" + ], + [ + "Josh Hickman", + "joshuahickman957@gmail.com", + "" + ], + [ + "Liz", + "", + "19192084530" + ], + [ + "Thom De'Fer", + "", + "9198027080" + ], + [ + "Apple Inc.", + "", + "1800MYAPPLE" + ] + ] +} \ No newline at end of file diff --git a/admin/test/results/teams/teams.teamsLocations.josh_ios15_ffs.20241104220313.json b/admin/test/results/teams/teams.teamsLocations.josh_ios15_ffs.20241104220313.json new file mode 100644 index 00000000..9c32872c --- /dev/null +++ b/admin/test/results/teams/teams.teamsLocations.josh_ios15_ffs.20241104220313.json @@ -0,0 +1,206 @@ +{ + "metadata": { + "module_name": "teams", + "artifact_name": "Teams Shared Locations", + "function_name": "teamsLocations", + "case_number": "josh_ios15_ffs", + "number_of_columns": 12, + "number_of_rows": 12, + "total_data_size_bytes": 6888, + "input_zip_path": "admin/test/cases/data/teams/testdata.teams.teamsLocations.josh_ios15_ffs.zip", + "start_time": "2024-11-04T22:03:13.913213+00:00", + "end_time": "2024-11-04T22:03:14.017468+00:00", + "run_time_seconds": 0.017634153366088867, + "last_commit": { + "hash": "de6fdbe64b57ef6f08cdcc5947c2bd89a6eb677c", + "author_name": "abrignoni", + "author_email": "abrignoni@gmail.com", + "date": "2023-10-10T21:56:23-04:00", + "message": "Timezone offset main code" + } + }, + "headers": [ + "Timestamp", + "From", + "Display Name", + "Content", + "Card URL", + "Card Title", + "Card Text", + "Card URL2", + "Latitude", + "Longitude", + "Card Expires", + "Device ID" + ], + "data": [ + [ + 1638647000, + "8:live:.cid.832804f1252ed9b2", + "ThisIs DFIR", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6602339_-78.8112703_301%20Crossway%20Ln%2C%20301%20Crossway%20Ln", + "Maps", + "301 Crossway Ln", + "https://urlp.asm.skype.com/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.6602339%2c-78.8112703%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.6602339%2c-78.8112703%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + null, + null, + null, + null + ], + [ + 1638647113, + "8:live:.cid.5638f9306b7b8594", + "ThisIs DfirTwo", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6580764_-78.8103737_106%20Fountain%20Ridge%20Pl%2C%20106%20Fountain%20Ridge%20Pl", + "Maps", + "106 Fountain Ridge Pl", + "https://urlp.asm.skype.com/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.6580764%2c-78.8103737%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.6580764%2c-78.8103737%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + null, + null, + null, + null + ], + [ + 1638647177, + "8:live:.cid.5638f9306b7b8594", + "ThisIs DfirTwo", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6629958_-78.8167595_My%20current%20location%2C%20120%20Flint%20Point%20Ln", + "Maps", + "My current location", + "https://urlp.asm.skype.com/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.6629958%2c-78.8167595%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.6629958%2c-78.8167595%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + "35.6629958", + "-78.8167595", + 1638620176, + "43c48367-3021-4aa7-862a-3b76d16bf44f" + ], + [ + 1638647537, + "8:live:.cid.832804f1252ed9b2", + "ThisIs DFIR", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6607699_-78.8310529_My%20current%20location%2C%20501%20St%20Croix%20Dr", + "Maps", + "My current location", + "https://urlp.asm.skype.com/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.6607699%2c-78.8310529%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.6607699%2c-78.8310529%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + "35.6607699", + "-78.8310529", + 1638620537, + "afb3c199-fa72-44b1-add4-9274ec608049" + ], + [ + 1670248018, + "8:live:.cid.832804f1252ed9b2", + "ThisIs DFIR", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6551144_-78.8538701_137%20Thomas%20Mill%20Rd%2C%20137%20Thomas%20Mill%20Rd", + "Maps", + "137 Thomas Mill Rd", + "https://us-prod.asyncgw.teams.microsoft.com/urlp/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.6551144%2c-78.8538701%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.6551144%2c-78.8538701%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + null, + null, + null, + null + ], + [ + 1670248078, + "8:live:.cid.832804f1252ed9b2", + "ThisIs DFIR", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6557741_-78.853738_My%20current%20location%2C%20M44W%2BCCF", + "Maps", + "My current location", + "https://us-prod.asyncgw.teams.microsoft.com/urlp/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.6557741%2c-78.853738%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.6557741%2c-78.853738%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + "35.6557741", + "-78.853738", + 1670221078, + "baf69df3-2b20-49f2-84b5-3f7022809765" + ], + [ + 1670248450, + "8:live:.cid.5638f9306b7b8594", + "ThisIs DfirTwo", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.667286_-78.8750147_105%20Lively%20Oaks%20Way%2C%20105%20Lively%20Oaks%20Way", + "Maps", + "105 Lively Oaks Way", + "https://us-prod.asyncgw.teams.microsoft.com/urlp/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.667286%2c-78.8750147%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.667286%2c-78.8750147%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + null, + null, + null, + null + ], + [ + 1670248567, + "8:live:.cid.5638f9306b7b8594", + "ThisIs DfirTwo", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6681226_-78.8778239_My%20current%20location%2C%201701%20Green%20Oaks%20Pkwy", + "Maps", + "My current location", + "https://us-prod.asyncgw.teams.microsoft.com/urlp/v1/url/content?url=https%3a%2f%2fmaps.googleapis.com%2fmaps%2fapi%2fstaticmap%3fcenter%3d35.6681226%2c-78.8778239%26zoom%3d15%26size%3d400x200%26markers%3dcolor%3ared%257C35.6681226%2c-78.8778239%26key%3dAIzaSyCkvtEv7EA_JdZaOCEWrc7Wm6N7M0nLvco", + "35.6681226", + "-78.8778239", + 1670221568, + "c7b923c2-8add-4a37-9fb3-05793018dc20" + ], + [ + 1684329012, + "8:live:.cid.832804f1252ed9b2", + "ThisIs DFIR", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.6291711725048_-78.79738913609707", + "Directions", + "116 Moss Creek Pl,\u00a0Fuquay-Varina,\u00a0NC", + "https://us-api.asm.skype.com/v1/objects/0-eus-d20-1308f0f6962b7b2bee476737cfccaa51/views/imgo", + "35.6291711725048", + "-78.79738913609707", + 1684305609, + "C85A6FB0-7F91-4170-800F-1AB5CFA82903" + ], + [ + 1684329467, + "8:live:.cid.5638f9306b7b8594", + "ThisIs DfirTwo", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.64467656406671_-78.81252548233516", + "Directions", + "100 Aldborough Way,\u00a0Holly Springs,\u00a0NC", + "https://us-api.asm.skype.com/v1/objects/0-eus-d17-718b896f15a188f1b73393e2c75b7936/views/imgo", + "35.64467656406671", + "-78.81252548233516", + 1684306065, + "83A2ADBB-F6A4-42D4-A10B-9F3D59754653" + ], + [ + 1684330048, + "8:live:.cid.5638f9306b7b8594", + "ThisIs DfirTwo", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.65531608947976_-78.83036282408914", + "Directions", + "309 Cayman Ave", + "https://us-api.asm.skype.com/v1/objects/0-eus-d4-f393ddcdfef3665aafc6f2242c23327d/views/imgo", + null, + null, + null, + null + ], + [ + 1684330223, + "8:live:.cid.832804f1252ed9b2", + "ThisIs DFIR", + "
", + "https://www.bing.com/maps?rtp=adr.~pos.35.66164532015291_-78.83040382854551", + "Directions", + "508 Sandy Point Way", + "https://us-api.asm.skype.com/v1/objects/0-eus-d18-9342a8f893cdbb5481a9ae0285d93de5/views/imgo", + null, + null, + null, + null + ] + ] +} \ No newline at end of file diff --git a/admin/test/results/teams/teams.teamsMessages.josh_ios15_ffs.20241104220314.json b/admin/test/results/teams/teams.teamsMessages.josh_ios15_ffs.20241104220314.json new file mode 100644 index 00000000..568667ec --- /dev/null +++ b/admin/test/results/teams/teams.teamsMessages.josh_ios15_ffs.20241104220314.json @@ -0,0 +1,598 @@ +{ + "metadata": { + "module_name": "teams", + "artifact_name": "Teams Messages", + "function_name": "teamsMessages", + "case_number": "josh_ios15_ffs", + "number_of_columns": 5, + "number_of_rows": 81, + "total_data_size_bytes": 11309, + "input_zip_path": "admin/test/cases/data/teams/testdata.teams.teamsMessages.josh_ios15_ffs.zip", + "start_time": "2024-11-04T22:03:14.263204+00:00", + "end_time": "2024-11-04T22:03:14.356771+00:00", + "run_time_seconds": 0.004088640213012695, + "last_commit": { + "hash": "de6fdbe64b57ef6f08cdcc5947c2bd89a6eb677c", + "author_name": "abrignoni", + "author_email": "abrignoni@gmail.com", + "date": "2023-10-10T21:56:23-04:00", + "message": "Timezone offset main code" + } + }, + "headers": [ + "Timestamp", + "Display Name", + "From", + "Message", + "Shared Media" + ], + "data": [ + [ + 1637783068, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
\n
\n \"Selected \n
\r\n\n
\n
", + "" + ], + [ + 1637783107, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Poor Pete. ", + "" + ], + [ + 1637783162, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
I'll send a picture.
", + "" + ], + [ + 1637783225, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
\n
\n \"Selected \n
\r\n\n
\n
", + "" + ], + [ + 1637783354, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "The logic is...interesting. ", + "" + ], + [ + 1637783420, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
I'll audio call you.
", + "" + ], + [ + 1637783673, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
Looks like the call failed. I'll try it again.
", + "" + ], + [ + 1637783830, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "That one failed too, maybe...?", + "" + ], + [ + 1637783914, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
\n
\n \"\ud83e\udd37\"\n
\n
", + "" + ], + [ + 1637783974, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "I will try to audio call you. ", + "" + ], + [ + 1637785340, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Try your audio call again. ", + "" + ], + [ + 1637786909, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Oh well. Nothing works. ", + "" + ], + [ + 1637786946, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Send a bad message and I will delete it. ", + "" + ], + [ + 1637787008, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
Here's your bad message.
", + "" + ], + [ + 1637787099, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Looks like only the sender can delete. ", + "" + ], + [ + 1637787129, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "", + "" + ], + [ + 1637787239, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Deleted. ", + "" + ], + [ + 1638647000, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
", + "" + ], + [ + 1638647113, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
", + "" + ], + [ + 1638647177, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
", + "" + ], + [ + 1638647466, + "", + "8:live:.cid.5638f9306b7b8594", + "Location sharing ended", + "" + ], + [ + 1638647537, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
", + "" + ], + [ + 1638647821, + "", + "8:live:.cid.832804f1252ed9b2", + "Location sharing ended", + "" + ], + [ + 1660692600, + "", + "19:204b46fc734948a796f40213b2e59e21@thread.v2", + "16606926006148:live:.cid.832804f1252ed9b216606926002308:live:.cid.832804f1252ed9b28:live:.cid.832804f1252ed9b2", + "" + ], + [ + 1660692600, + "", + "19:204b46fc734948a796f40213b2e59e21@thread.v2", + "16606926009468:live:.cid.832804f1252ed9b2True", + "" + ], + [ + 1660692600, + "", + "19:204b46fc734948a796f40213b2e59e21@thread.v2", + "16606926007118:live:.cid.832804f1252ed9b2DFIR family", + "" + ], + [ + 1660692602, + "", + "19:204b46fc734948a796f40213b2e59e21@thread.v2", + "16606926021568:live:.cid.832804f1252ed9b2URL@https://us-api.asm.skype.com/v1/objects/0-wus-d3-4caefafab6664c06dfa331c304479adf/views/avatar_fullsize", + "" + ], + [ + 1668540913, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "You there?", + "" + ], + [ + 1668540965, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Yes sir, I am. ", + "" + ], + [ + 1668541028, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Awesome. We can make this quick then. ", + "" + ], + [ + 1668541104, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Agreed. Let me find a picture. Hang on. ", + "" + ], + [ + 1668541174, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
\n
\n \"Media\" \n
\n
", + "" + ], + [ + 1668541229, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Is that accurate?", + "" + ], + [ + 1668541329, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
I have no idea. It's got to be generally correct though.
", + "" + ], + [ + 1668541420, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Certainly looks that way. Lol. ", + "" + ], + [ + 1668541538, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
\r\n
\r\nThisIs DfirTwo\r\n

Certainly looks that way. Lol.

\r\n
\r\nThe numbers may not be, but the trend probably is.
", + "" + ], + [ + 1668541600, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
I'll send a picture. Standby.
", + "" + ], + [ + 1668541688, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
\n
\n \"Media\" \n
\n
", + "" + ], + [ + 1668541862, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Still accurate in 2022. ", + "" + ], + [ + 1668541968, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
I'd say. Especially the part about the Christmas music.
", + "" + ], + [ + 1668542061, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
Yep. I'll audio call you. Hang on.
", + "" + ], + [ + 1668542371, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "My turn. ", + "" + ], + [ + 1668542608, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Video call time. ", + "" + ], + [ + 1668542834, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
I'll video call you.
", + "" + ], + [ + 1668543051, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "", + "" + ], + [ + 1668543082, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Then delete the one you just sent. ", + "" + ], + [ + 1668543198, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Done. ", + "" + ], + [ + 1670248018, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
", + "" + ], + [ + 1670248078, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
", + "" + ], + [ + 1670248307, + "", + "8:live:.cid.832804f1252ed9b2", + "Location sharing ended", + "" + ], + [ + 1670248450, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
", + "" + ], + [ + 1670248567, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
", + "" + ], + [ + 1670248836, + "", + "8:live:.cid.5638f9306b7b8594", + "Location sharing ended", + "" + ], + [ + 1682689489, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "You here yet?", + "" + ], + [ + 1682689653, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "I am. Battery and all.", + "" + ], + [ + 1682689697, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Yeah, how is it doing this morning?", + "" + ], + [ + 1682689776, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Still horrible. It\u2019s drained like 30 percent in the last hour or so.", + "" + ], + [ + 1682689829, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Yikes! \u00a0That is bad. When we do location stuff you\u2019ll need to be plugged in.", + "" + ], + [ + 1682690018, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "This phone is retiring after this image.", + "" + ], + [ + 1682690055, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Definitely. I\u2019ll send you a picture. Hang on.", + "" + ], + [ + 1682690228, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
", + "" + ], + [ + 1682691522, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "That picture is truth.", + "" + ], + [ + 1682691569, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Thought you might like it.", + "" + ], + [ + 1682691618, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "I will audio call you now.", + "" + ], + [ + 1682691710, + "", + "8:live:.cid.832804f1252ed9b2", + "Call Logs for Call 6d8fa7a2-9a2e-4cf2-8c87-cc946fbe5663", + "" + ], + [ + 1682691783, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "What happened?", + "" + ], + [ + 1682691853, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "No clue. I\u2019ll try it again.", + "" + ], + [ + 1682691942, + "", + "8:live:.cid.832804f1252ed9b2", + "Call Logs for Call 1019490c-938a-45be-a3ec-b10069e7c2fc", + "" + ], + [ + 1682692056, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "Well, it happened again. You try audio calling and see what happens.", + "" + ], + [ + 1682692211, + "", + "8:live:.cid.832804f1252ed9b2", + "Call Logs for Call 7f99d235-02b8-4c1c-adb7-f94038019166", + "" + ], + [ + 1682692488, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "Well, that worked. You can try video calling me now.", + "" + ], + [ + 1682692733, + "", + "8:live:.cid.832804f1252ed9b2", + "Call Logs for Call c808711b-e466-4eaf-86c2-2eae9b1cc39b", + "" + ], + [ + 1682692945, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "I\u2019ll video call. Standby.", + "" + ], + [ + 1682693144, + "", + "8:live:.cid.832804f1252ed9b2", + "Call Logs for Call 43f78ea2-b67f-4e73-af63-eeeabab8ed68", + "" + ], + [ + 1682693224, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "I am sending you this message. I will delete it in a minute.", + "" + ], + [ + 1684329012, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
", + "" + ], + [ + 1684329357, + "", + "8:live:.cid.832804f1252ed9b2", + "Location sharing ended", + "" + ], + [ + 1684329467, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
", + "" + ], + [ + 1684329757, + "", + "8:live:.cid.5638f9306b7b8594", + "Location sharing ended", + "" + ], + [ + 1684330048, + "ThisIs DfirTwo", + "8:live:.cid.5638f9306b7b8594", + "
", + "" + ], + [ + 1684330223, + "ThisIs DFIR", + "8:live:.cid.832804f1252ed9b2", + "
", + "" + ] + ] +} \ No newline at end of file diff --git a/admin/test/results/teams/teams.teamsUser.josh_ios15_ffs.20241104220314.json b/admin/test/results/teams/teams.teamsUser.josh_ios15_ffs.20241104220314.json new file mode 100644 index 00000000..a429db1f --- /dev/null +++ b/admin/test/results/teams/teams.teamsUser.josh_ios15_ffs.20241104220314.json @@ -0,0 +1,39 @@ +{ + "metadata": { + "module_name": "teams", + "artifact_name": "Teams User Information", + "function_name": "teamsUser", + "case_number": "josh_ios15_ffs", + "number_of_columns": 3, + "number_of_rows": 2, + "total_data_size_bytes": 56, + "input_zip_path": "admin/test/cases/data/teams/testdata.teams.teamsUser.josh_ios15_ffs.zip", + "start_time": "2024-11-04T22:03:14.187212+00:00", + "end_time": "2024-11-04T22:03:14.262576+00:00", + "run_time_seconds": 0.0019540786743164062, + "last_commit": { + "hash": "de6fdbe64b57ef6f08cdcc5947c2bd89a6eb677c", + "author_name": "abrignoni", + "author_email": "abrignoni@gmail.com", + "date": "2023-10-10T21:56:23-04:00", + "message": "Timezone offset main code" + } + }, + "headers": [ + "Timestamp Last Sync", + "Display Name", + "Phone Number" + ], + "data": [ + [ + 1684415520, + "ThisIs DFIR", + "19195794674" + ], + [ + 1684328990, + "ThisIs DfirTwo", + "" + ] + ] +} \ No newline at end of file diff --git a/scripts/artifacts/teams.py b/scripts/artifacts/teams.py index 3506db25..1c41a582 100644 --- a/scripts/artifacts/teams.py +++ b/scripts/artifacts/teams.py @@ -1,3 +1,59 @@ +__artifacts_v2__ = { + "teamsMessages": { + "name": "Teams Messages", + "description": "Microsoft Teams messages and shared media", + "author": "", + "version": "1.0", + "category": "Microsoft Teams", + "paths": ('*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*', + '*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/Downloads/*/Images/*'), + "output_types": "standard", + "chatParams": { + "threadDiscriminatorColumn": "Display Name", + "textColumn": "Message", + "timeColumn": "Timestamp", + "senderColumn": "From", + "mediaColumn": "Shared Media" + } + }, + "teamsContacts": { + "name": "Teams Contacts", + "description": "Microsoft Teams contact list", + "author": "", + "version": "1.0", + "category": "Microsoft Teams", + "paths": ('*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*',), + "output_types": "standard" + }, + "teamsUser": { + "name": "Teams User Information", + "description": "Microsoft Teams user profile and sync data", + "author": "", + "version": "1.0", + "category": "Microsoft Teams", + "paths": ('*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*',), + "output_types": "standard" + }, + "teamsCalls": { + "name": "Teams Call Logs", + "description": "Microsoft Teams call history", + "author": "", + "version": "1.0", + "category": "Microsoft Teams", + "paths": ('*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*',), + "output_types": "standard" + }, + "teamsLocations": { + "name": "Teams Shared Locations", + "description": "Microsoft Teams shared location data", + "author": "", + "version": "1.0", + "category": "Microsoft Teams", + "paths": ('*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*',), + "output_types": "all" + } +} + import sqlite3 import io import json @@ -5,250 +61,407 @@ import re import shutil import datetime +from scripts.ilapfuncs import logfunc, convert_ts_human_to_timezone_offset, artifact_processor, open_sqlite_db_readonly import nska_deserialize as nd -import scripts.artifacts.artGlobals - -from packaging import version -from scripts.artifact_report import ArtifactHtmlReport -from scripts.ilapfuncs import logfunc, logdevinfo, timeline, kmlgen, tsv, is_platform_windows, open_sqlite_db_readonly - -def get_teams(files_found, report_folder, seeker, wrap_text, timezone_offset): - CacheFile = 0 +@artifact_processor +def teamsMessages(files_found, report_folder, seeker, wrap_text, timezone_offset): + if not files_found: + return + + data_list = [] + cache_file = None + db_file = None + + # Find required files for file_found in files_found: file_found = str(file_found) - if file_found.endswith('.sqlite'): - databasedata = file_found - if file_found.endswith('CacheFile'): - CacheFile = file_found + db_file = file_found + elif file_found.endswith('CacheFile'): + cache_file = file_found + + if not db_file: + logfunc('No Teams database found') + return - if CacheFile != 0: - with open(CacheFile ,'rb') as nsfile: - nsplist = nd.deserialize_plist(nsfile) + # Initialize cache dictionary + nsplist = {} + if cache_file: + try: + with open(cache_file, 'rb') as nsfile: + nsplist = nd.deserialize_plist(nsfile) + except Exception as e: + logfunc(f'Error parsing CacheFile: {str(e)}') + nsplist = {} - db = open_sqlite_db_readonly(databasedata) + db = open_sqlite_db_readonly(db_file) cursor = db.cursor() + cursor.execute(''' - SELECT - datetime('2001-01-01', "ZARRIVALTIME" || ' seconds'), - ZIMDISPLAYNAME, - ZCONTENT - from ZSMESSAGE + SELECT + datetime('2001-01-01', "ZARRIVALTIME" || ' seconds') as timestamp, + ZIMDISPLAYNAME, + ZCONTENT, + ZFROM + FROM ZSMESSAGE + ORDER BY timestamp ''') - all_rows = cursor.fetchall() - usageentries = len(all_rows) - data_list = [] - - if usageentries > 0: - for row in all_rows: - thumb ='' + for row in cursor.fetchall(): + timestamp = convert_ts_human_to_timezone_offset(row[0], timezone_offset) + display_name = row[1] if row[1] else '' + content = row[2] if row[2] else '' + sender = row[3] if row[3] else '' + thumb = '' + + # Process media content + if content and '
' - except: - logfunc(f'Error on block lines 47 - 57, content on {row[0]}') - data_list.append((row[0], row[1], row[2], thumb)) - - description = 'Teams Messages' - report = ArtifactHtmlReport('Teams Messages') - report.start_artifact_report(report_folder, 'Teams Messages', description) - report.add_script() - data_headers = ('Timestamp', 'Name', 'Message', 'Shared Media') - report.write_artifact_data_table(data_headers, data_list, file_found, html_no_escape=['Shared Media'] -) - report.end_artifact_report() + try: + shutil.copy2(match, report_folder) + data_file_name = os.path.basename(match) + thumb = f'' + except Exception as e: + logfunc(f'Error copying media file: {str(e)}') + except Exception as e: + logfunc(f'Error processing image for message at {timestamp}: {str(e)}') - tsvname = 'Microsoft Teams Messages' - tsv(report_folder, data_headers, data_list, tsvname) - - tlactivity = 'Microsoft Teams Messages' - timeline(report_folder, tlactivity, data_list, data_headers) - else: - logfunc('No Microsoft Teams Messages data available') + data_list.append((timestamp, display_name, sender, content, thumb)) + + db.close() + + data_headers = ( + ('Timestamp', 'datetime'), + 'Display Name', + 'From', + 'Message', + 'Shared Media' + ) + + return data_headers, data_list, db_file + +@artifact_processor +def teamsContacts(files_found, report_folder, seeker, wrap_text, timezone_offset): + if not files_found: + return + + data_list = [] + db_file = None + + for file_found in files_found: + if file_found.endswith('.sqlite'): + db_file = file_found + break + + if not db_file: + logfunc('No Teams database found') + return + + db = open_sqlite_db_readonly(db_file) + cursor = db.cursor() cursor.execute(''' - SELECT - ZDISPLAYNAME, - zemail, - ZPHONENUMBER - from - ZDEVICECONTACTHASH + SELECT + ZDISPLAYNAME, + ZEMAIL, + ZPHONENUMBER + FROM ZDEVICECONTACTHASH ''') - all_rows = cursor.fetchall() - usageentries = len(all_rows) + for row in cursor.fetchall(): + display_name = row[0] if row[0] else '' + email = row[1] if row[1] else '' + + # Clean phone number + phone = row[2] if row[2] else '' + if phone: + # Remove common formatting characters + phone = phone.replace('(', '').replace(')', '').replace('-', '') + phone = phone.replace(' ', '').replace('.', '').replace('+', '') + + data_list.append((display_name, email, phone)) + + db.close() + + data_headers = ( + 'Display Name', + ('Email', 'phonenumber'), + ('Phone Number', 'phonenumber') + ) + + return data_headers, data_list, db_file + +@artifact_processor +def teamsUser(files_found, report_folder, seeker, wrap_text, timezone_offset): + if not files_found: + return + data_list = [] + db_file = None - if usageentries > 0: - for row in all_rows: - data_list.append((row[0], row[1], row[2])) - - description = 'Teams Contact' - report = ArtifactHtmlReport('Teams Contact') - report.start_artifact_report(report_folder, 'Teams Contact', description) - report.add_script() - data_headers = ('Display Name', 'Email', 'Phone Number') - report.write_artifact_data_table(data_headers, data_list, file_found) - report.end_artifact_report() - - tsvname = 'Teams Contact' - tsv(report_folder, data_headers, data_list, tsvname) - - else: - logfunc('No Teams Contact data available') + for file_found in files_found: + if file_found.endswith('.sqlite'): + db_file = file_found + break + + if not db_file: + logfunc('No Teams database found') + return + + db = open_sqlite_db_readonly(db_file) + cursor = db.cursor() cursor.execute(''' - SELECT - datetime('2001-01-01', "ZTS_LASTSYNCEDAT" || ' seconds'), - ZDISPLAYNAME, - ZTELEPHONENUMBER - from zuser + SELECT + datetime('2001-01-01', "ZTS_LASTSYNCEDAT" || ' seconds'), + ZDISPLAYNAME, + ZTELEPHONENUMBER + FROM ZUSER ''') - all_rows = cursor.fetchall() - usageentries = len(all_rows) + for row in cursor.fetchall(): + timestamp = convert_ts_human_to_timezone_offset(row[0], timezone_offset) + display_name = row[1] if row[1] else '' + phone = row[2] if row[2] else '' + data_list.append((timestamp, display_name, phone)) + + db.close() + + data_headers = ( + ('Timestamp Last Sync', 'datetime'), + 'Display Name', + ('Phone Number', 'phonenumber') + ) + + return data_headers, data_list, db_file + +@artifact_processor +def teamsCalls(files_found, report_folder, seeker, wrap_text, timezone_offset): + if not files_found: + return + data_list = [] + db_file = None + + for file_found in files_found: + if file_found.endswith('.sqlite'): + db_file = file_found + break + + if not db_file: + logfunc('No Teams database found') + return + + db = open_sqlite_db_readonly(db_file) + cursor = db.cursor() - if usageentries > 0: - for row in all_rows: - data_list.append((row[0], row[1], row[2])) - - description = 'Teams User' - report = ArtifactHtmlReport('Teams User') - report.start_artifact_report(report_folder, 'Teams User', description) - report.add_script() - data_headers = ('Timestamp Last Sync', 'Display Name', 'Phone Number') - report.write_artifact_data_table(data_headers, data_list, file_found) - report.end_artifact_report() - - tsvname = 'Microsoft Teams User' - tsv(report_folder, data_headers, data_list, tsvname) - - tlactivity = 'Microsoft Teams User' - timeline(report_folder, tlactivity, data_list, data_headers) - - else: - logfunc('No Teams User data available') - cursor.execute(''' - SELECT - ZCOMPOSETIME, - zfrom, - ZIMDISPLAYNAME, - zcontent, - ZPROPERTIES - from ZSMESSAGE, ZMESSAGEPROPERTIES - where ZSMESSAGE.ZTSID = ZMESSAGEPROPERTIES.ZTSID - order by ZCOMPOSETIME + SELECT + ZCOMPOSETIME, + ZFROM, + ZIMDISPLAYNAME, + ZCONTENT, + ZPROPERTIES + FROM ZSMESSAGE + JOIN ZMESSAGEPROPERTIES ON ZSMESSAGE.ZTSID = ZMESSAGEPROPERTIES.ZTSID + WHERE ZPROPERTIES IS NOT NULL + ORDER BY ZCOMPOSETIME ''') - all_rows = cursor.fetchall() - usageentries = len(all_rows) - data_list_calls = [] - data_list_cards = [] - data_list_unparsed = [] + for row in cursor.fetchall(): + try: + compose_time = convert_ts_human_to_timezone_offset(row[0].replace('T', ' '), timezone_offset) + plist_file_object = io.BytesIO(row[4]) + + try: + plist = nd.deserialize_plist(plist_file_object) + except Exception as e: + logfunc(f'Failed to read plist: {str(e)}') + continue + + if 'call-log' in plist: + try: + if isinstance(plist['call-log'], dict): + datacalls = plist['call-log'] + else: + datacalls = json.loads(plist['call-log']) + + call_start = convert_ts_human_to_timezone_offset( + datacalls.get('startTime', '').replace('T', ' '), + timezone_offset + ) if datacalls.get('startTime') else '' + + call_connect = convert_ts_human_to_timezone_offset( + datacalls.get('connectTime', '').replace('T', ' '), + timezone_offset + ) if datacalls.get('connectTime') else '' + + call_end = convert_ts_human_to_timezone_offset( + datacalls.get('endTime', '').replace('T', ' '), + timezone_offset + ) if datacalls.get('endTime') else '' + + data_list.append(( + compose_time, + row[1] if row[1] else '', # from + row[2] if row[2] else '', # display_name + row[3] if row[3] else '', # content + call_start, + call_connect, + call_end, + datacalls.get('callDirection', ''), + datacalls.get('callType', ''), + datacalls.get('callState', ''), + datacalls.get('originator', ''), + datacalls.get('target', ''), + datacalls.get('originatorParticipant', {}).get('displayName', ''), + datacalls.get('targetParticipant', {}).get('displayName', '') + )) + except Exception as e: + logfunc(f'Error processing call log data: {str(e)} - Data: {plist["call-log"]}') + + except Exception as e: + logfunc(f'Error processing row: {str(e)}') + + db.close() - if usageentries > 0: - for row in all_rows: - plist = '' - composetime = row[0].replace('T',' ') + data_headers = ( + ('Compose Timestamp', 'datetime'), + 'From', + 'Display Name', + 'Content', + ('Call Start', 'datetime'), + ('Call Connect', 'datetime'), + ('Call End', 'datetime'), + 'Call Direction', + 'Call Type', + 'Call State', + 'Call Originator', + 'Call Target', + 'Call Originator Name', + 'Call Participant Name' + ) + + return data_headers, data_list, db_file + +@artifact_processor +def teamsLocations(files_found, report_folder, seeker, wrap_text, timezone_offset): + if not files_found: + return + + data_list = [] + db_file = None + + for file_found in files_found: + if file_found.endswith('.sqlite'): + db_file = file_found + break + + if not db_file: + logfunc('No Teams database found') + return + + db = open_sqlite_db_readonly(db_file) + cursor = db.cursor() + + cursor.execute(''' + SELECT + ZCOMPOSETIME, + ZFROM, + ZIMDISPLAYNAME, + ZCONTENT, + ZPROPERTIES + FROM ZSMESSAGE + JOIN ZMESSAGEPROPERTIES ON ZSMESSAGE.ZTSID = ZMESSAGEPROPERTIES.ZTSID + WHERE ZPROPERTIES IS NOT NULL + ORDER BY ZCOMPOSETIME + ''') + + for row in cursor.fetchall(): + try: + compose_time = convert_ts_human_to_timezone_offset(row[0].replace('T', ' '), timezone_offset) plist_file_object = io.BytesIO(row[4]) - if row[4].find(b'NSKeyedArchiver') == -1: - if sys.version_info >= (3, 9): - plist = plistlib.load(plist_file_object) - else: - plist = biplist.readPlist(plist_file_object) - else: + + try: + plist = nd.deserialize_plist(plist_file_object) + except Exception as e: + logfunc(f'Failed to read plist: {str(e)}') + continue + + if 'cards' in plist: try: - plist = nd.deserialize_plist(plist_file_object) - except (nd.DeserializeError, nd.biplist.NotBinaryPlistException, nd.biplist.InvalidPlistException, - nd.plistlib.InvalidFileException, nd.ccl_bplist.BplistError, ValueError, TypeError, OSError, OverflowError) as ex: - logfunc(f'Failed to read plist for {row[4]}, error was:' + str(ex)) - if 'call-log' in plist: - datacalls = json.loads(plist['call-log']) - callstart = (datacalls.get('startTime')) - callstart = callstart.replace('T',' ') - callconnect = (datacalls.get('connectTime')) - callconnect = callconnect.replace('T',' ') - callend = (datacalls['endTime']) - callend = callend.replace('T',' ') - calldirection = (datacalls['callDirection']) - calltype = (datacalls['callType']) - callstate = (datacalls['callState']) - calloriginator = (datacalls['originator']) - calltarget = (datacalls['target']) - calloriginatordname = (datacalls['originatorParticipant']['displayName']) - callparticipantdname = (datacalls['targetParticipant']['displayName']) - data_list_calls.append((composetime, row[1], row[2], row[3], callstart, callconnect, callend, calldirection, calltype, callstate, calloriginator, calltarget, calloriginatordname, callparticipantdname)) - elif 'cards' in plist: - cards = json.loads(plist['cards']) - cardurl = (cards[0]['content']['body'][0]['selectAction']['url']) - cardtitle = (cards[0]['content']['body'][0]['selectAction']['title']) - cardtext = (cards[0]['content']['body'][1]['text']) - cardurl2 = (cards[0]['content']['body'][0]['url']) - if (cards[0]['content']['body'][0].get('id')) is not None: - idcontent = json.loads(cards[0]['content']['body'][0]['id']) - cardlat = (idcontent.get('latitude')) - cardlong = (idcontent.get('longitude')) - cardexpires = (idcontent.get('expiresAt')) - cardexpires = datetime.datetime.fromtimestamp(cardexpires / 1000) - carddevid = (idcontent.get('deviceId')) - data_list_cards.append((composetime, row[1], row[2], row[3], cardurl, cardtitle, cardtext, cardurl2, cardlat, cardlong, cardexpires, carddevid)) - else: - data_list_unparsed.append((composetime, row[1], row[2], row[3], plist)) - - description = 'Microsoft Teams Call Logs' - report = ArtifactHtmlReport('Microsoft Teams Call Logs') - report.start_artifact_report(report_folder, 'Teams Call Logs', description) - report.add_script() - data_headers = ('Compose Timestamp', 'From', 'Display Name', 'Content',' Call Start', 'Call Connect', 'Call End', 'Call Direction', 'Call Type', 'Call State', 'Call Originator', 'Call Target', 'Call Originator Name', 'Call Participant Name') - report.write_artifact_data_table(data_headers, data_list_calls, file_found) - report.end_artifact_report() - - tsvname = 'Microsoft Teams Call Logs' - tsv(report_folder, data_headers, data_list_calls, tsvname) - - tlactivity = 'Microsoft Teams Call Logs' - timeline(report_folder, tlactivity, data_list_calls, data_headers) - - description = 'Microsoft Teams Shared Locations' - report = ArtifactHtmlReport('Microsoft Teams Shared Locations') - report.start_artifact_report(report_folder, 'Teams Shared Locations', description) - report.add_script() - data_headers = ('Timestamp', 'From', 'Display Name', 'Content','Card URL', 'Card Title', 'Card Text', 'Card URL2', 'Latitude', 'Longitude', 'Card Expires', 'Device ID') - report.write_artifact_data_table(data_headers, data_list_cards, file_found) - report.end_artifact_report() - - tsvname = 'Microsoft Teams Shared Locations' - tsv(report_folder, data_headers, data_list_cards, tsvname) - - tlactivity = 'Microsoft Teams Shared Locations' - timeline(report_folder, tlactivity, data_list_cards, data_headers) - - kmlactivity = 'Microsoft Teams Shared Locations' - kmlgen(report_folder, kmlactivity, data_list_cards, data_headers) - - else: - logfunc('No Microsoft Teams Call Logs & Cards data available') - + cards = json.loads(plist['cards']) + card = cards[0]['content']['body'][0] + + card_url = card.get('selectAction', {}).get('url', '') + card_title = card.get('selectAction', {}).get('title', '') + card_text = cards[0]['content']['body'][1].get('text', '') + card_url2 = card.get('url', '') + + card_lat = None + card_long = None + card_expires = None + card_devid = None + + if card.get('id'): + try: + id_content = json.loads(card['id']) + card_lat = id_content.get('latitude') + card_long = id_content.get('longitude') + expires_ms = id_content.get('expiresAt') + if expires_ms: + card_expires = convert_ts_human_to_timezone_offset( + datetime.datetime.fromtimestamp(expires_ms / 1000).strftime('%Y-%m-%d %H:%M:%S'), + timezone_offset + ) + card_devid = id_content.get('deviceId') + except Exception as e: + logfunc(f'Error processing card ID data: {str(e)}') + + data_list.append(( + compose_time, + row[1] if row[1] else '', # from + row[2] if row[2] else '', # display_name + row[3] if row[3] else '', # content + card_url, + card_title, + card_text, + card_url2, + card_lat, + card_long, + card_expires, + card_devid + )) + except Exception as e: + logfunc(f'Error processing card data: {str(e)}') + + except Exception as e: + logfunc(f'Error processing row: {str(e)}') db.close() -__artifacts__ = { - "teams": ( - "Microsoft Teams", - ('*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/*/Skype*.sqlite*','*/mobile/Containers/Shared/AppGroup/*/SkypeSpacesDogfood/Downloads/*/Images/*'), - get_teams) -} - - \ No newline at end of file + data_headers = ( + ('Timestamp', 'datetime'), + 'From', + 'Display Name', + 'Content', + 'Card URL', + 'Card Title', + 'Card Text', + 'Card URL2', + 'Latitude', + 'Longitude', + ('Card Expires', 'datetime'), + 'Device ID' + ) + + return data_headers, data_list, db_file +