Windows.Forensics.Shellbags

Windows uses the Shellbag keys to store user preferences for GUI folder display within Windows Explorer.

This artifact uses the raw registry parser to inspect various user registry hives around the filesystem for BagMRU keys. Different OS versions may have slightly different locations for the MRU keys.


name: Windows.Forensics.Shellbags
description: |
  Windows uses the Shellbag keys to store user preferences for GUI
  folder display within Windows Explorer.

  This artifact uses the raw registry parser to inspect various user
  registry hives around the filesystem for BagMRU keys. Different OS
  versions may have slightly different locations for the MRU keys.

reference:
  - https://www.sans.org/blog/computer-forensic-artifacts-windows-7-shellbags/

parameters:
  - name: SearchSpecs
    type: csv
    description: Define locations of MRU bags in various registries.
    default: |
      HiveGlob,KeyGlob
      C:/Users/*/NTUSER.dat,\Software\Microsoft\Windows\Shell\BagMRU\**
      C:/Users/*/AppData/Local/Microsoft/Windows/UsrClass.dat,\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\**


imports:
  # Link files use the same internal format as shellbags so we import
  # the profile here.
  - Windows.Forensics.Lnk

sources:
  - query: |
      LET AllHives = SELECT * FROM foreach(row=SearchSpecs,
        query={
            SELECT OSPath AS HivePath, KeyGlob
            FROM glob(globs=HiveGlob)
            WHERE log(message="Inspecting hive " + HivePath)
        })

      LET ShellValues = SELECT * FROM foreach(row=AllHives,
        query={
           SELECT OSPath, Data, ModTime
           FROM glob(
              root=pathspec(DelegatePath=HivePath),
              globs=KeyGlob,
              accessor="raw_reg")
           WHERE Data.type =~ "BINARY" AND OSPath.Basename =~ "^[0-9]+$"
        })

      LET ParsedValues = SELECT
          OSPath.Dirname AS KeyPath,
          parse_binary(profile=Profile, filename=Data.value,
                       accessor="data", struct="ItemIDList") as _Parsed,
          base64encode(string=Data.value) AS _RawData, ModTime
      FROM ShellValues

      LET AllResults <= SELECT KeyPath,
        _Parsed.ShellBag.Description AS Description,
        _Parsed, _RawData, ModTime
      FROM ParsedValues

      // Recursive function to join path components together.
      // Limit recursion depth just in case.
      LET FormPath(MRUPath, Description, Depth) = SELECT * FROM if(
        condition=Depth < 20,
        then={SELECT * FROM chain(
          b={
            SELECT MRUPath, Description, Depth,
              -- Signify unknown component as ?
              Description.LongName || Description.ShortName || "?" AS Name
            FROM scope()
          },
          c={
            SELECT * FROM foreach(row={
              SELECT KeyPath, Description, Depth
              FROM AllResults
              WHERE KeyPath = MRUPath.Dirname
              LIMIT 1
            }, query={
              SELECT * FROM FormPath(MRUPath=KeyPath,
                                     Description=Description, Depth=Depth + 1)
            })
          })
          ORDER BY Depth DESC
          LIMIT 10
        })

        // Now display all hits and their reconstructed path
        LET ReconstructedPath = SELECT ModTime, KeyPath, Description, {
           SELECT * FROM FormPath(
                MRUPath=KeyPath, Description=Description, Depth=0)
        } AS Chain, _RawData, _Parsed
        FROM AllResults

        SELECT ModTime,
           KeyPath AS _OSPath,
           KeyPath.DelegatePath AS Hive,
           KeyPath.Path AS KeyPath, Description,
               join(array=Chain.Name, sep=" -> ") AS Path,
               _RawData, _Parsed
        FROM ReconstructedPath
        ORDER BY Path

column_types:
  - name: _RawData
    type: base64