-
Notifications
You must be signed in to change notification settings - Fork 95
Add Windows MSN plugin #1084
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add Windows MSN plugin #1084
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6a7a08e
add msn plugin
JSCU-CNI da1ec60
Apply suggestions from code review
JSCU-CNI 3e05184
use target.resolve instead
JSCU-CNI 6c6b71e
revert test
JSCU-CNI 60dc26a
Merge branch 'main' into feature/add-msn-chat-plugin
JSCU-CNI 9b4be0b
__init__.py
JSCU-CNI 6882374
Merge branch 'main' into feature/add-msn-chat-plugin
Schamper File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| from typing import Union | ||
|
|
||
| from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension | ||
| from dissect.target.helpers.record import create_extended_descriptor | ||
| from dissect.target.plugin import NamespacePlugin | ||
|
|
||
| COMMON_FIELDS = [ | ||
| ("datetime", "ts"), | ||
| ("string", "client"), | ||
| ("string", "account"), | ||
| ("string", "sender"), | ||
| ("string", "recipient"), | ||
| ] | ||
|
|
||
| GENERIC_USER_FIELDS = [ | ||
| ("datetime", "ts_mtime"), | ||
| ("string", "client"), | ||
| ("string", "account"), | ||
| ] | ||
|
|
||
| GENERIC_ATTACHMENT_FIELDS = [ | ||
| *COMMON_FIELDS, | ||
| ("path", "attachment"), | ||
| ("string", "description"), | ||
| ] | ||
|
|
||
| GENERIC_MESSAGE_FIELDS = [ | ||
| *COMMON_FIELDS, | ||
| ("string", "message"), | ||
| ] | ||
|
|
||
| ChatUserRecord = create_extended_descriptor([UserRecordDescriptorExtension])( | ||
| "chat/user", | ||
| GENERIC_USER_FIELDS, | ||
| ) | ||
|
|
||
| ChatMessageRecord = create_extended_descriptor([UserRecordDescriptorExtension])( | ||
| "chat/message", | ||
| GENERIC_MESSAGE_FIELDS, | ||
| ) | ||
|
|
||
| ChatAttachmentRecord = create_extended_descriptor([UserRecordDescriptorExtension])( | ||
| "chat/attachment", | ||
| GENERIC_ATTACHMENT_FIELDS, | ||
| ) | ||
|
|
||
| ChatRecord = Union[ChatUserRecord, ChatMessageRecord, ChatAttachmentRecord] | ||
|
|
||
|
|
||
| class ChatPlugin(NamespacePlugin): | ||
| __namespace__ = "chat" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Iterator | ||
|
|
||
| from defusedxml import ElementTree as ET | ||
|
|
||
| from dissect.target import Target | ||
| from dissect.target.exceptions import UnsupportedPluginError | ||
| from dissect.target.helpers.fsutil import TargetPath | ||
| from dissect.target.plugin import export | ||
| from dissect.target.plugins.apps.chat.chat import ( | ||
| ChatAttachmentRecord, | ||
| ChatMessageRecord, | ||
| ChatPlugin, | ||
| ) | ||
| from dissect.target.plugins.general.users import UserDetails | ||
|
|
||
|
|
||
| class MSNPlugin(ChatPlugin): | ||
| """Microsoft MSN Messenger plugin. | ||
|
|
||
| Supports the following versions on Windows XP and Windows 7: | ||
| - Windows Live Messenger (WLM) 2009 | ||
| - MSN 7.5 | ||
|
|
||
| Other versions might work but have not been tested. Does not support ``Messenger Plus! Live`` artifacts. | ||
| Tested using Escargot (https://escargot.chat). | ||
|
|
||
| Resources: | ||
| - https://en.wikipedia.org/wiki/Microsoft_Messenger_service | ||
| - https://en.wikipedia.org/wiki/MSN_Messenger | ||
| - http://computerforensics.parsonage.co.uk/downloads/MSNandLiveMessengerArtefactsOfConversations.pdf | ||
| """ | ||
|
|
||
| __namespace__ = "msn" | ||
|
|
||
| DATA_PATH = "Application Data\\Microsoft\\MSN Messenger" | ||
| HIST_PATH = "My Documents\\My Received Files" | ||
|
|
||
| def __init__(self, target: Target): | ||
| super().__init__(target) | ||
| self.installs = list(self.find_installs()) | ||
|
|
||
| def find_installs(self) -> Iterator[tuple[UserDetails, TargetPath]]: | ||
| for user_details in self.target.user_details.all_with_home(): | ||
| if (path := self.target.fs.path(user_details.user.home).joinpath(self.DATA_PATH)).exists(): | ||
| for profile in path.iterdir(): | ||
| if profile.is_dir(): | ||
| yield user_details, profile | ||
|
|
||
| def check_compatible(self) -> None: | ||
| if not self.installs: | ||
| raise UnsupportedPluginError("No Microsoft MSN installs found on target") | ||
|
|
||
| @export(record=[ChatMessageRecord, ChatAttachmentRecord]) | ||
| def history(self) -> Iterator[ChatMessageRecord | ChatAttachmentRecord]: | ||
| """Yield MSN chat history messages. | ||
|
|
||
| Chat history artifacts can be found in: | ||
| - ``$HOME/My Documents/My Received Files/MsnMsgr.txt`` | ||
| - ``$HOME/My Documents/My Received Files/$username$PassportID/History/*.xml`` | ||
| """ | ||
|
|
||
| for user_details, profile in self.installs: | ||
| if not (hist_root := self.target.fs.path(user_details.user.home).joinpath(self.HIST_PATH)).exists(): | ||
| self.target.log.warning( | ||
| "User %s does not have saved MSN chat history: directory %s does not exist", | ||
| user_details.user.name, | ||
| hist_root, | ||
| ) | ||
| continue | ||
|
|
||
| hist_dir = None | ||
| for item in hist_root.iterdir(): | ||
| if item.is_dir() and (hist_dir := item.name).endswith(profile.name): | ||
| for hist_file in hist_root.joinpath(hist_dir).joinpath("History").glob("*.xml"): | ||
| try: | ||
| xml = ET.fromstring(hist_file.read_text()) | ||
| except Exception as e: | ||
| self.target.log.warning("XML file %s is malformed: %s", hist_file, e) | ||
| continue | ||
|
|
||
| for entry in xml: | ||
| common = { | ||
| "ts": entry.attrib.get("DateTime", 0), | ||
| "client": self.__namespace__, | ||
| "account": profile.name, | ||
| "sender": entry.find(".//From/User").get("FriendlyName"), | ||
| "_user": user_details.user, | ||
| "_target": self.target, | ||
| } | ||
|
|
||
| if entry.tag == "Message": | ||
| yield ChatMessageRecord( | ||
| **common, | ||
| recipient=entry.find(".//To/User").get("FriendlyName"), | ||
| message=entry.find(".//Text").text, | ||
| ) | ||
|
|
||
| elif entry.tag in ["Invitation", "InvitationResponse"]: | ||
| if (file := entry.find(".//File")) is not None: | ||
| yield ChatAttachmentRecord( | ||
| **common, | ||
| recipient=None, # unknown with Invitations | ||
| attachment=file.text, | ||
| description=entry.find(".//Text").text, | ||
| ) | ||
|
|
||
|
|
||
| def convert_email(string: str) -> int: | ||
| """Convert MSN email address to 10 digit Passport ID.""" | ||
| num = 0 | ||
| for char in string.lower(): | ||
| num = num * 101 + ord(char) | ||
| num -= (num // 4294967296) * 4294967296 | ||
| return num |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Git LFS file not shown
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| from datetime import datetime, timezone | ||
|
|
||
| from dissect.target import Target | ||
| from dissect.target.filesystem import VirtualFilesystem | ||
| from dissect.target.plugins.apps.chat.msn import MSNPlugin, convert_email | ||
| from tests._utils import absolute_path | ||
|
|
||
|
|
||
| def test_msn(target_win_users: Target, fs_win: VirtualFilesystem) -> None: | ||
| """test if we parse MSN Chat messages on Windows XP correctly.""" | ||
|
|
||
| morpheus_id = convert_email("morpheus@matrix.internal") | ||
| neo_id = convert_email("neo@matrix.internal") | ||
|
|
||
| assert morpheus_id == 2450688751 | ||
| assert neo_id == 4092013818 | ||
|
|
||
| fs_win.makedirs(f"Users/John/Application Data/Microsoft/MSN Messenger/{morpheus_id}") | ||
| fs_win.map_file( | ||
| f"Users/John/My Documents/My Received Files/morpheus{morpheus_id}/History/neo{neo_id}.xml", | ||
| absolute_path("_data/plugins/apps/chat/msn/history.xml"), | ||
| ) | ||
|
|
||
| target_win_users.add_plugin(MSNPlugin) | ||
| assert len(target_win_users.msn.installs) == 1 | ||
|
|
||
| results = list(target_win_users.msn.history()) | ||
| assert len(results) == 35 | ||
|
|
||
| assert results[0].username == "John" | ||
| assert results[0].hostname is None | ||
|
|
||
| assert results[0].ts == datetime(2025, 4, 1, 13, 37, 0, tzinfo=timezone.utc) | ||
| assert results[0].client == "msn" | ||
| assert results[0].account == str(morpheus_id) | ||
| assert results[0].sender == "morpheus@matrix.internal" | ||
| assert results[0].recipient == "neo@matrix.internal" | ||
|
|
||
| assert [(r.sender.replace("@matrix.internal", ""), r.message) for r in results] == [ | ||
| ( | ||
| "morpheus", | ||
| "At last.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Welcome, Neo. As you no doubt have guessed, I am Morpheus.", | ||
| ), | ||
| ( | ||
| "neo", | ||
| "It's an honor.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "No, the honor is mine. Please. Come. Sit.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "I imagine, right now, you must be feeling a bit like Alice, tumbling down the rabbit hole?", | ||
| ), | ||
| ( | ||
| "neo", | ||
| "You could say that.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "I can see it in your eyes. You have the look of a man who accepts " | ||
| "what he sees because he is expecting to wake up.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Ironically, this is not far from the truth. But I'm getting ahead of " | ||
| "myself. Can you tell me, Neo, why are you here?", | ||
| ), | ||
| ( | ||
| "neo", | ||
| "You're Morpheus. You're a legend. Most hackers would die to meet you.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Yes. Thank you. But I think we both know there's more to it than that. Do you believe in fate, Neo?", | ||
| ), | ||
| ( | ||
| "neo", | ||
| "No.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Why not?", | ||
| ), | ||
| ( | ||
| "neo", | ||
| "Because I don't like the idea that I'm not in control of my life.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "I know exactly what you mean.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Let me tell you why you are here. You have come because you know something.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "What you know you can't explain but you feel it.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "You've felt it your whole life, felt that something is wrong with the world.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "You don't know what, but it's there like a splinter in your mind, " | ||
| "driving you mad. It is this feeling that brought you to me.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Do you know what I'm talking about?", | ||
| ), | ||
| ( | ||
| "neo", | ||
| "The Matrix?", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Do you want to know what it is?", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "The Matrix is everywhere, it's all around us, here even in this room.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "You can see it out your window or on your television. You feel it " | ||
| "when you go to work, or go to church or pay your taxes.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "It is the world that has been pulled over your eyes to blind you from the truth.", | ||
| ), | ||
| ( | ||
| "neo", | ||
| "What truth?", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "That you are a slave, Neo. Like everyone else, you were born into " | ||
| "bondage, kept inside a prison that you cannot smell, taste, or touch.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "A prison for your mind.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Unfortunately, no one can be told what the Matrix is.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "You have to see it for yourself.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "This is your last chance. After this, there is no going back.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "You take the blue pill and the story ends.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "You wake in your bed and you believe whatever you want to believe.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Remember that all I am offering is the truth. Nothing more.", | ||
| ), | ||
| ( | ||
| "morpheus", | ||
| "Follow me.", | ||
| ), | ||
| ] |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.