Skip to content
Please update to the latest release 0.77.3 to address Multiple CVEs.
Windows.Forensics.TabState

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 )