Windows.Applications.MouseWithoutBorders

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.

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