Windows.Forensics.NotepadParser

Parse the Windows 11 Notepad state files.

Based on the research work published by ogmini. This artifact parses the TabState and WindowState files and also uploads them for preservation.


name: Windows.Forensics.NotepadParser
description: |
  Parse the Windows 11 Notepad state files.

  Based on the research work published by ogmini. This artifact parses
  the TabState and WindowState files and also uploads them for
  preservation.

reference:
  - https://github.com/ogmini/Notepad-State-Library
  - https://ogmini.github.io/tags.html#Windows-Notepad

author: ogmini https://ogmini.github.io/ and Mike Cohen

parameters:
  - name: WindowStateGlob
    default: C:/Users/*/AppData/Local*/Packages/Microsoft.WindowsNotepad*/LocalState/WindowState/*[01].bin
  - name: TabStateGlob
    default: C:/Users/*/AppData/Local*/Packages/Microsoft.WindowsNotepad*/LocalState/TabState/*.bin

export: |
    LET WinNotepadProfile <= '''[
      [WindowStateHeader, 0, [
        [Signature, 0, String, {
            length: 2,
        }],
        [Sequence, 2, leb128],
        [BytesToCRC, "x=>x.`@Sequence`.EndOf", leb128],
        [NumberTabs, "x=>x.`@BytesToCRC`.EndOf + 1", leb128],
        [Tabs, "x=>x.NumberTabs.EndOf", Array, {
           type: GUID,
           count: "x=>x.NumberTabs.Value",
           sentinel: "x=>x.__D1 = 0 AND x.__D2 = 0",
        }],
        [ActiveTab, "x=>x.Tabs.EndOf", leb128],
      ]],

      ["GUID", 16, [
        ["__D1", 0, "uint32"],
        ["__D2", 4, "uint16"],
        ["__D3", 6, "uint16"],
        ["__D4", 8, "String", {"term": "", "length": 2}],
        ["__D5", 10, "String", {"term": "", "length": 6}],
        ["Value", 0, "Value", { "value": "x=>upcase(string=
            format(format='%08x-%04x-%04x-%02x-%02x',
              args=[x.__D1, x.__D2, x.__D3, x.__D4, x.__D5]))" }],
      ]],

      [TabStateHeader, 0, [
        [Signature, 0, String, {
            length: 2,
        }],
        [Sequence, 2, leb128],
        [Type, "x=>x.Sequence.EndOf", leb128],
        [Header, 0, Union, {
           selector: "x=>x.Type.Value",
           choices: {
             "0": "TabStateHeaderUnsaved",
             "1": "TabStateHeaderSaved",
           },
        }],
      ]],

      [TabStateHeaderUnsaved, 0, [
        [Signature, 0, String, {
            length: 2,
        }],
        [Sequence, 2, leb128],
        [Type, "x=>x.Sequence.EndOf", leb128],

        [CursorPosition, "x=>x.Type.EndOf + 1", CursorPosition],
        [ConfigurationBlock, "x=>x.CursorPosition.EndOf", ConfigurationBlock],
        [ContentLength, "x=>x.ConfigurationBlock.EndOf", leb128],
        [Content, "x=>x.ContentLength.EndOf", String, {
            encoding: "utf16",
            length: "x=>x.ContentLength.Value * 2",
            max_length: 100000,
        }],
        [Unsaved, "x=>x.`@Content`.EndOf", uint8],
        [CRC32, "x=>x.`@Unsaved`.EndOf", uint32],
      ]],

      [TabStateHeaderSaved, 0, [
        [HeaderType, 0, Value, {
          value: "Saved",
        }],
        [Signature, 0, String, {
            length: 2,
        }],
        [Sequence, 2, leb128],
        [Type, "x=>x.Sequence.EndOf", leb128],
        [FilePathLength, "x=>x.Type.EndOf", leb128],
        [FilePath, "x=>x.FilePathLength.EndOf", String, {
            encoding: "utf16",
            length: "x=>x.FilePathLength.Value * 2",
        }],
        [SavedFileContentLength, "x=>x.`@FilePath`.EndOf", leb128],
        [EncodingType, "x=>x.SavedFileContentLength.EndOf", uint8],
        [CarriageReturnType, "x=>x.`@EncodingType`.EndOf", uint8],
        [__Timestamp, "x=>x.`@CarriageReturnType`.EndOf", leb128],
        [Timestamp, 0, Value, {
            value: "x=>timestamp(winfiletime=x.__Timestamp.Value)",
        }],
        [FileHash, "x=>x.__Timestamp.EndOf", String, {
            length: 32, term: "",
        }],
        [CursorPosition, "x=>x.`@FileHash`.EndOf + 2", CursorPosition],
        [ConfigurationBlock, "x=>x.CursorPosition.EndOf", ConfigurationBlock],
        [ContentLength, "x=>x.ConfigurationBlock.EndOf", leb128],
        [Content, "x=>x.ContentLength.EndOf", String, {
            encoding: "utf16",
            length: "x=>x.ContentLength.Value * 2",
            max_length: 100000,
        }],
        [Unsaved, "x=>x.`@Content`.EndOf", uint8],
        [CRC32, "x=>x.`@Unsaved`.EndOf", uint32],
        [UnsavedBuffers, "x=>x.`@CRC32`.EndOf", Array, {
           type: UnsavedBuffer,
           count: 100,
           sentinel: "x=>x.AdditionAction.Value = 0",
        }],
      ]],
      [ConfigurationBlock, "x=>x.MoreOptions.EndOf + x.MoreOptions.Value - x.OffsetOf", [
         ["WordWrap", 0, uint8],
         ["RightToLeft", 1, uint8],
         [ShowUnicode, 2, uint8],
         [MoreOptions, 3, leb128],
      ]],
      [CursorPosition, "x=>x.SelectionEndIndex.EndOf - x.OffsetOf", [
        [SelectionStartIndex, 0, leb128],
        [SelectionEndIndex, "x=>x.`@SelectionStartIndex`.RelEndOf", leb128],
      ]],

      [UnsavedBuffer, "x=>x.`@AddedChars`.EndOf + 4 - x.OffsetOf", [
        [Offset, 0, Value, {
          value: "x=>x.OffsetOf",
        }],
        [CursorPosition, 0, leb128],
        [DeletionAction, "x=>x.`@CursorPosition`.RelEndOf", leb128],
        [AdditionAction, "x=>x.`@DeletionAction`.RelEndOf", leb128],
        [AddedChars, "x=>x.`@AdditionAction`.RelEndOf", String, {
            encoding: "utf16",
            length: "x=>x.AdditionAction.Value * 2",
            max_length: 100000,
        }]
      ]],
    ]
    '''

column_types:
- name: Upload
  type: preview_upload


sources:
- name: TabState
  query:
    LET AllFiles = SELECT OSPath, Mtime, Size,
        upload(file=OSPath, mtime=Mtime) AS Upload
    FROM glob(globs=TabStateGlob)

    LET AllTabState = SELECT *, parse_binary(
      filename=OSPath,
      offset=0,
      profile=WinNotepadProfile,
      struct="TabStateHeader") AS _TabState
    FROM AllFiles
    WHERE _TabState.Header.Signature

    SELECT *,
       _TabState.Header.FilePath AS EditedFile,
       _TabState.Header.Timestamp AS EditTimestamp,
       _TabState.Header.Content AS Content,
       _TabState.Header.UnsavedBuffers.AddedChars AS UnsavedBuffers,
       Upload
    FROM AllTabState

- name: WindowState
  query:
    LET AllFiles = SELECT OSPath, Mtime, Size,
        upload(file=OSPath, mtime=Mtime) AS Upload
    FROM glob(globs=WindowStateGlob)

    LET AllTabState = SELECT *, parse_binary(
      filename=OSPath,
      offset=0,
      profile=WinNotepadProfile,
      struct="WindowStateHeader") AS _WindowState

    FROM AllFiles
    WHERE _WindowState.Signature = "NP"


    SELECT *,
       _WindowState.NumberOfTabs AS NumberOfTabs,
       _WindowState.Tabs.Value.Value AS Tabs,
       _WindowState.ActiveTab AS ActiveTab,
       Upload
    FROM AllTabState