Skip to content
Please update to the latest release 0.77.2 to address Multiple CVEs.
Windows.Applications.MouseWithoutBorders

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