This artifact parses Microsoft Mouse Without Borders (MWB) runtime logs, module interface logs and the settings file.
name: Windows.Applications.MouseWithoutBorders
description: |
This artifact parses Microsoft Mouse Without Borders (MWB) runtime logs, module interface logs and the settings file.
* It's recommended to use the Sort and Stack features to handle repeated events effectively.
author: Mohamed Sultan
reference:
- https://0xsultan.github.io/dfir/Exfiltrate-Without-Borders/
type: CLIENT
parameters:
- name: UserFilter
default: .
type: regex
description: Regex to filter specific users (default all users)
- name: DateAfter
type: timestamp
description: Only show logs after this date
- name: DateBefore
type: timestamp
description: Only show logs before this date
- name: RuntimeLogs
default: 'C:\Users\*\AppData\Local\Microsoft\PowerToys\MouseWithoutBorders\Logs\*\*.*'
description: Glob to runtime logs.
- name: ModuleLogFiles
default: 'C:\Users\*\AppData\Local\Microsoft\PowerToys\MouseWithoutBorders\LogsModuleInterface\*.*'
description: Glob pattern for module interface log files
- name: SettingsFile
default: 'C:\Users\*\AppData\Local\Microsoft\PowerToys\MouseWithoutBorders\settings.json'
description: Glob to settings.
precondition: SELECT OS FROM info() WHERE OS = 'windows'
sources:
- name: RuntimeLogs
description: Parses MWB runtime logs
query: |
-- message line e.g. 05/17 07:29:43.152(1)PowerToys Started!
LET MessageRegex = '''(?P<MonthDay>\d{2}/\d{2}) (?P<Time>\d{2}:\d{2}:\d{2}\.\d+)\((?P<ThreadID>\d+)\)(?P<Message>.+)'''
-- message patterns
LET ImportantPatterns = '''PowerToys Started|TCP listening on port|Keyboard/Mouse hooks installed|Helper process|New connection from client|tcpClient\.Connect.*Unable to connect|Cannot resolve.*machine|==> '''
-- parse runtime log files
LET parsed_messages = SELECT * FROM foreach(
row={
SELECT OSPath
FROM glob(globs=RuntimeLogs)
WHERE OSPath =~ UserFilter
},
query={
SELECT
OSPath,
parse_string_with_regex(string=Line, regex=MessageRegex) AS Parsed
FROM parse_lines(filename=OSPath)
WHERE Line =~ MessageRegex
AND parse_string_with_regex(string=Line, regex=MessageRegex).Message =~ ImportantPatterns
}
)
SELECT
OSPath,
timestamp(string=format(format="%s/%s %s", args=[ regex_replace(source=OSPath, re=".*Log_(\\d{4})-\\d{2}-\\d{2}\\.log.*", replace="$1"), Parsed.MonthDay, Parsed.Time ])) AS Timestamp,
regex_replace(source=Parsed.Message, re="^==> ", replace="File Transfer: ") AS EventDescription
FROM parsed_messages
WHERE Parsed.Message != ""
AND (NOT DateAfter OR timestamp(string=format(format="%s/%s %s", args=[ regex_replace(source=OSPath, re=".*Log_(\\d{4})-\\d{2}-\\d{2}\\.log.*", replace="$1"), Parsed.MonthDay, Parsed.Time ])) > DateAfter)
AND (NOT DateBefore OR timestamp(string=format(format="%s/%s %s", args=[ regex_replace(source=OSPath, re=".*Log_(\\d{4})-\\d{2}-\\d{2}\\.log.*", replace="$1"), Parsed.MonthDay, Parsed.Time ])) < DateBefore)
ORDER BY Timestamp
- name: ModuleInterfaceLogs
description: Parses MWB module interface logs
query: |
-- Define regex for module interface logs
LET ModuleLogRegex = '''^\[(?P<Timestamp>\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d+)\]\s+\[p-(?P<PID>\d+)\]\s+\[t-(?P<TID>\d+)\]\s+\[(?P<Level>\w+)\]\s+(?P<Message>.*)$'''
-- Find module interface log files
LET module_files = SELECT * FROM foreach(
row={
SELECT OSPath
FROM glob(globs=ModuleLogFiles)
WHERE OSPath =~ UserFilter
},
query={
SELECT OSPath
FROM glob(globs=ModuleLogFiles)
WHERE OSPath =~ UserFilter
}
)
-- Parse module logs
LET parsed_module = SELECT * FROM foreach(
row=module_files,
query={
SELECT
OSPath,
parse_string_with_regex(
string=Line,
regex=ModuleLogRegex
) AS Parsed
FROM parse_lines(filename=OSPath)
WHERE Line =~ "^\\["
}
)
-- Extract structured data
SELECT
OSPath,
timestamp(string=Parsed.Timestamp) AS Timestamp,
-- Parsed.PID AS PID,
-- Parsed.TID AS TID,
Parsed.Message AS RawMessage
FROM parsed_module
WHERE Parsed.Timestamp
AND (NOT DateAfter OR timestamp(string=Parsed.Timestamp) > DateAfter)
AND (NOT DateBefore OR timestamp(string=Parsed.Timestamp) < DateBefore)
- name: Configuration
description: Extracts MWB configuration from settings.json
query: |
LET config_files = SELECT OSPath
FROM glob(globs="C:\\Users\\*\\AppData\\Local\\Microsoft\\PowerToys\\MouseWithoutBorders\\settings.json")
WHERE OSPath =~ UserFilter
LET parsed_config = SELECT
OSPath,
stat(filename=OSPath).Mtime AS ModificationTime,
parse_json(data=read_file(filename=OSPath)) AS Config
FROM config_files
SELECT
OSPath,
ModificationTime,
Config.properties.SecurityKey.value AS SecurityKey,
Config.properties.MachineMatrixString AS ConnectedMachines,
Config.properties.MachinePool.value AS MachinePool_ID,
-- Extract CurrentMachineName (first machine name from MachinePool)
if(condition=Config.properties.MachinePool.value AND Config.properties.MachinePool.value =~ "^([^:,]+):",
then=regex_replace(source=Config.properties.MachinePool.value, re="^([^:,]+):.*", replace="$1"),
else="Unknown") AS CurrentMachineName,
Config.properties.TCPPort.value AS TCPPort,
Config.properties.ShareClipboard.value AS ShareClipboard,
Config.properties.TransferFile.value AS TransferFile,
Config.properties.UseService.value AS UseService,
Config.properties.ValidateRemoteMachineIP.value AS ValidateRemoteIP,
Config.properties.SameSubnetOnly.value AS SameSubnetOnly,
Config.properties.FirstRun.value AS FirstRun,
-- Name2IP field (most time is empty)
if(condition=Config.properties.Name2IP.value AND Config.properties.Name2IP.value != "",
then=Config.properties.Name2IP.value,
else="") AS IPAddressMapping
FROM parsed_config
column_types:
- name: Timestamp
type: timestamp
- name: ModificationTime
type: timestamp