Windows.Forensics.TabState

This artifact parses notepad TabState files in available in Windows 11.

In Windows 11, notepad has implemented a feature to repopulate previously open notepad tabs - both saved and unsaved. This data is stored on disk and provides an interesting opportunity for DFIR practitioners.


name: Windows.Forensics.TabState
author: Matt Green - @mgreen27
description: |
   This artifact parses notepad TabState files in available in Windows 11.
   
   In Windows 11, notepad has implemented a feature to repopulate previously 
   open notepad tabs - both saved and unsaved. This data is stored on disk and 
   provides an interesting opportunity for DFIR practitioners.
   
reference:
  - https://medium.com/@mahmoudsoheem/new-digital-forensics-artifact-from-windows-notepad-527645906b7b
  - https://www.youtube.com/watch?v=zSSBbv2fc2s
  
type: CLIENT

parameters:
   - name: TargetGlob
     description: Target glob for notepad TabState bin files.
     default: C:\Users\*\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\*.bin
   - name: ContentRegex
     description: Content filter regex to select which TabState files return a row.
     type: regex
     default: .
   - name: FilenameRegex
     description: Filter regex to select Saved filename path. ```^$``` returns only unsaved files.
     type: regex
     default: .
   - name: UploadFile
     description: If selected will upload TabState file.
     type: bool

export: |
    LET TSProfile = '''[
        ["TabState", 0, [
            ["__Magic", 0, "String", {"length": 3, "term_hex" : "FFFFFF" }],
            ["__Saved", 3, "char"],
            ["__FileNameSize", 4, "int8"],
            ["__Filename", 5, "String", {
                                        encoding: "utf8",
                                        length: "x=>x.__FileNameSize * 2", 
                                        term_hex : "000000",
                                    }],
            ["Filename",0,"Value",{"value": "x=>if(condition= x.__Saved > 0, then=utf16(string=x.__Filename),else='')"}],
            
            ["__HeaderPrefix", "x=>5 + len(list=x.__Filename)", "String",{"term_hex": "0100", length: 1000, max_length: 1000}],
            ["__DataOffset",0,"Value",{ "value": "x=>5 + len(list=x.__Filename) + len(list=x.__HeaderPrefix)"}],
            ["__Data", "x=> x.__DataOffset + 5", "String", {
                                        encoding: "utf8",
                                        length: 100000,
                                        max_length: 100000, 
                                        "term_hex" : "000000000000000000000000" 
                                    }],
            ["StateData",0,"Value",{ "value": "x=>utf16(string=x.__Data[:(len(list=x.__Data) - 5)])"}],
        ]]]'''


sources:
  - precondition:
      SELECT OS From info() where OS = 'windows'

    query: |
      LET results = SELECT OSPath, Name,Mtime,Atime,Ctime,Btime,
            parse_binary(filename=OSPath,profile=TSProfile,struct='TabState') as Parsed
        FROM glob(globs=expand(path=TargetGlob)) 
        WHERE NOT IsDir 
            AND NOT OSPath =~'''\.(0|1)\.bin$'''
            AND Parsed.StateData =~ ContentRegex
            AND Parsed.Filename =~ FilenameRegex
        
      SELECT 
        Name,Mtime,Atime,Ctime,Btime,
        Parsed.Filename as SavedFilename,
        Parsed.StateData as StateData,
        OSPath
      FROM if(condition= UploadFile,
        then={ 
            SELECT *, upload(file=OSPath) as Upload 
            FROM results 
        },
        else= results )