ChogeLog

主にセキュリティ関係の記録やWrite-up。たまーに日記も。

ShellBags(一部)を自力で解析してみた

今回は興味本位でShellBagsを解析してみたので、解析の過程で理解したShellBagsの構造(のごく一部)について紹介します。
ShellBagsについては日本語の情報がないのはもちろんですが、英語でも深掘りした情報が中々ないので想像以上にツラかった…。

ShellBagsとは

ShellBagsとは、Windowsエクスプローラーで開いたフォルダに関する情報を保持しているデータです。ユーザーによるフォルダのアクセス履歴が分かるため、フォレンジックでよく利用されます。
ShellBagsは以下のレジストリに保存されています。

NTUSER.DAT

\Software\Microsoft\Windows\Shell\BagMRU
\Software\Microsoft\Windows\Shell\Bags

USRCLASS.DAT

\Local Settings\Software\Microsoft\Windows\Shell\BagMRU
\Local Settings\Software\Microsoft\Windows\Shell\Bags

実際にフォレンジック作業を行う際は、一般的なフォレンジックツールか、Eric Zimmerman's toolsShellBags Explorerで解析するのが普通だと思います。

ちなみにShellBagsの中身はこんな感じ↓で、軽い気持ちで『パーサー作ってみるか~』と思った自分は後悔しました😇

USRCLASS.DATの\Local Settings\Software\Microsoft\Windows\Shell\BagMRUの例

とはいえ調べてみると面白かったので、ここからは調査して分かったことを紹介していきます。

ShellBagsの構造を調査

調査概要

今回はShellBagsのデータからフォルダパスを復元することを目的とします。
また、解析対象はUSRCLASS.DATの \Local Settings\Software\Microsoft\Windows\Shell\BagMRUに存在するPC(マイコンピュータ)内のドライブ配下に限定して調査を行いました。(全てを網羅するのはかなりの労力がかかりそうなので…)

ShellBagsの全体的な構造

BagMRU配下は実際のフォルダと同様の階層構造になっています。
ただし、キーは全て数字になっており、フォルダ名は親キーの対応する値の中に記録されています。
たとえば、自分の環境ではDesktop(PC\C:\Users\[ユーザープロファイル名]\Desktop)のキーは\BagMRU\1\4\1\0\1となっており、「Desktop」というフォルダ名は\BagMRU\1\4\1\0の値1の中に存在しています。(図の右側)

ShellBagsの構造(Desktopの例)

そのため、BagMRU配下を再帰的に探索すればフォルダパスを構築することができそうです。
が、バイナリデータの構成は全て同じというわけではないため、簡単にはいきません…。

ちなみに、各キーにはMRUListExという値があり、ここには最近使用されたフォルダ順が記録されています。詳細は下記の記事を参照ください。(今回の調査では関係ないので割愛)

medium.com

次からは各キーのバイナリデータについて見ていきます。

BagMRU

まずはルートであるBagMRUキーです。
BagMRUキーの中に「PC」(マイコンピュータ)の情報が存在します。
PCの場合は0x3の値(ソートインデックス)が50になっているそうです(参考)。
自分の環境では値「1」が「PC」になっていました。

BagMRUキーの値「1」

もしくはGUIDで識別することも可能で、「PC」のGUIDは{20d04fe0-3aea-1069-a2d8-08002b30309d}となっています(参考)。

GUIDはそのまま記載されているわけではなく、以下のような形式になっています。そのため変換して比較する必要があります。(めんどい…)

[0x4~0x7 little]-[0x8~0x9 little]-[0xA~0xB little]-[0xC~0xD big]-[0xE~0x13 big]

※little:リトルエンディアン、big:ビッグエンディアン

PC(マイコンピュータ)

次にPCです。自分の環境だと\BagMRU\1キーを見ます。
この中にドライブレターの情報が存在しており、Cドライブは値「4」にありました。
ドライブレターの場合は0x2(クラスタイプ識別子)の値が2Fで、0x3~0x4にドライブ名が記載されています。(図の②③)
ただし、クラスタイプ識別子は2F以外の場合もあるそうです…(参考

PCキーの値「4」

ちなみに、先頭2バイト(図の①)はバイナリデータのサイズを表します。(サイズに先頭2バイトは含めない?)
以降のキーも先頭3バイトはサイズとクラスタイプ識別子を表します。

Cドライブ

次にCドライブを見てみます。先ほどCドライブのキーは「4」であることが分かったので、\BagMRU\1\4を見ます。
\BagMRU\1\4の各値は下図のようになっており、Cドライブ直下のフォルダ情報を確認できます。

Cドライブの各値

バイナリデータの構造

ここで例としてUsersフォルダ(上図の値「1」)のバイナリデータを詳しく見てみます。
Usersのバイナリデータは以下のような構造になっていました。

Usersのデータ構造
①シェルサイズ(先頭2バイトを除外したこのデータのサイズ?)
クラスタイプ識別子(0x31=フォルダ)
③ファイルサイズ(フォルダは0?)
④最終更新日時
⑤ファイル属性(0x11=読み取り専用のディレクトリ)
⑥短縮名(フォルダ名が短い場合は⑯と同じ値になる)
⑦拡張ブロック(⑧以降)のサイズ
⑧拡張のバージョン
⑨拡張のシグネチャ(0xBEEF0004)
⑩作成日時
⑪最終アクセス日時
⑫OSバージョン(Win 8.1以降は0x002E?)
⑬MFTエントリ番号
⑭MFTシーケンス番号
⑮フォルダ名(⑯)以降のサイズ?
⑯フォルダ名
ローカライズされた名前(@shell32.dll,-21813)
⑱拡張ブロック(⑦)のオフセット

Usersフォルダに限らず、ドライブレター以降の各フォルダは、ほぼ全て上図と同じ構造でした。

また、フォルダ名に日本語(マルチバイト?)が含まれる場合は、②クラスタイプ識別子が0x35で、⑥短縮名と⑯フォルダ名がUTF-16LEで表現されます。

フォルダ名が日本語の場合
フォルダ名をUTF-16LEでデコード

日時データについて

バイナリデータには最終更新日時、作成日時、最終アクセス日時の3つの日時データが含まれています。
ただし、FAT32のタイムスタンプ形式になっており、ぱっと見ても分からない値になっています。
FAT32のタイムスタンプは4バイトで表現可能ですが、2秒単位でしか記録できない特徴があります。
具体的な計算方法はここでは割愛しますが、自分は以下のサイトが参考になりました。

qiita.com

たとえばPythonだと以下のようなコードで計算可能です。

import datetime

fat32time = bytes.fromhex('0E518320')
date_bytes = fat32time[:2]
time_bytes = fat32time[2:]
date = int.from_bytes(date_bytes, byteorder='little')
time = int.from_bytes(time_bytes, byteorder='little')
year = (date >> 9) + 1980
month = (date >> 5) & 0b1111
day = date & 0b11111
hour = time >> 11
minute = (time >> 5) & 0b111111
second = (time & 0b11111) * 2
converted_time = datetime.datetime(year, month, day, hour, minute, second, tzinfo=datetime.timezone.utc)
print(converted_time.astimezone(datetime.timezone(datetime.timedelta(hours=9))))

上記のコードでは、16進数の「0E518320」という値を計算しており、最終的に「2020-08-14 13:04:06+09:00」が出力されます。

おまけ(First InteractedとLast Interactedについて)

ShellBags Explorerで解析した際、「First Interacted」と「Last Interacted」の日時が記録されていることがあります。

ShellBags Explorerの画面

この日時をどこから取ってきているのか気になったので調べたところ、以下の日時を使用していることが分かりました。

  • First Interacted:サブフォルダが存在しないフォルダの場合、そのフォルダ(キー)のLast writeを使用する。
  • Last Interacted:MRUListExで最新のフォルダの場合、その親フォルダ(キー)のLast writeを使用する。
    DownloadsのFirst InteractedとLast Interacted

この例のDownloadsフォルダの場合、Downloadsフォルダ(\BagMRU\1\4\3)のLast writeがFirst Interactedになり、Cドライブ(\BagMRU\1\4)のLast writeがLast Interactedになります。

パーサー(試作品)を開発してみた

ShellBagsの構造が分かったらあとはコードを書くだけ…ということでShellBagsからフォルダパスを復元するテストコードを書いてみました!
※全てのフォルダではなく、UsrClass.datの各ドライブ配下だけを解析します。
※OSなどの環境によっては動かない可能性が十分にあります。

github.com

ざっくりコード解説

  • regipyを使ってレジストリを解析する。
  • UsrClass.datを読み込ませるとBagMRUの情報を取得し、そこからPC(マイコンピュータ)のキーを探す。
  • PCを見つけると、次に各ドライブのキーを探す。
  • あとは各ドライブ配下のフォルダ(キー)を再帰的に解析し、フォルダパスを構築していくだけ。

ShellBagsからは様々な情報を取得できるのですが、今回は最低限として、フォルダのフルパス、サブキー(サブフォルダ)数、作成日時、更新日時、最終アクセス日時(JST)だけ抽出してみました。

実行例

コードを実行して解析に成功すると、以下のようにPC(マイコンピュータ)のキーと、各ドライブのキーを出力します。

出力例

以下は生成されたCSVの例です。

出力されるCSVの例

とりあえず各ドライブ配下だけですが、フォルダパスと日時データを抽出することができました!
ドライブ以外のフォルダについても、構造を調べることで同様に解析できると思いますが、そこまでする理由もモチベーションもないので今回はここまでとしておきます…。

実際にフォレンジックする際はShellBags Explorerを使えば十分ですからね😂

まとめ

今回はShellBagsの構造を調べて、自力でフォルダパスを復元してみました!
最初はShellBagsのパーサーを開発してみようかな~と軽い気持ちで始めたのですが、レジストリのバイナリデータを見た瞬間に後悔しました😇
複雑かつ情報も少ないので、気軽に手を出していいものではなかったですね…
とはいえ学びは多かったので結果良かったです!

参考サイト

イベントID 1029に記録されるユーザー名に注意

Microsoft-Windows-TerminalServices-RDPClient/OperationalイベントID 1029に記録されるユーザー名について、単純には解析できなかったのでメモとして記録しておきます。

なお、本記事の内容は以下の記事とほぼ同じ内容なので、英語が得意な方はこちら👇を読むことをオススメします。

nullsec.us

イベントID 1029の概要

イベントID 1029は、RDP接続を試行する際に接続元の端末に記録されます。
記録される内容は下図の通りで、接続しようとしたユーザー名をハッシュ化&エンコードした値が記録されます。

イベントID 1029の例(元のユーザー名は「TEST」)

注意点として、存在しないユーザー名を入力した場合や、RDP接続に失敗した場合も記録されます。

イベントID 1029の解析

ログに Base64(SHA256(UserName)) is = … と記載されているので、ユーザー名のSHA256を計算した後にBase64エンコードすればこの値が生成されると考えられます。
ということで、先ほどの例で使用した「TEST」を計算してみます。

「TEST」をSHA256→Base64エンコード

…が、計算結果は
OTRlZTA1OTMzNWU1ODdlNTAxY2M0YmY5MDYxM2UwODE0ZjAwYTdiMDhiYzdjNjQ4ZmQ4NjVhMmFmNmEyMmNjMg==
となっており、イベントログに記録された値
zzV6ChUo2bTS/UpOyeSYs3PXpzH5m4DwLJuB+bpyHbY=
と明らかに異なります。

そこでイベントID 1029について調べてみたところ、どうやら実際は以下の流れで計算されていることが分かりました。

  1. ユーザー名をUTF-16LEエンコードする
  2. 1のSHA256を計算し、バイナリで出力する
  3. 2をBase64エンコードする

つまり、実際は Base64(SHA256binary(UTF-16LE(UserName)) と書いた方が正しいようです。( Base64(SHA256(UserName)) じゃ分からんやろ…)

ということで、 CyberChefBase64(SHA256binary(UTF-16LE(UserName)) を計算してみたところ、ログの値と同じ結果になりました🎉

Base64(SHA256binary(UTF-16LE(UserName))を計算した結果

Pythonの場合は以下のようなコードで計算可能です。

import hashlib
import base64

# 計算したいユーザー名
username = 'TEST'

# UTF-16LEにエンコード
utf16_username = username.encode('utf-16le')

# SHA256を計算し、バイナリ形式で取得
hash_username = hashlib.sha256(utf16_username).digest()

# Base64エンコード
base64_username = base64.b64encode(hash_username)

# UTF-8にデコード
result = base64_username.decode('utf-8')

print('ユーザー名: ' + username)
print('Base64(SHA256binary(UTF-16LE(UserName)) is = ' + result)

上記のコードを実行すると以下のように出力されます。

Pythonで計算した結果

まとめ

本記事ではMicrosoft-Windows-TerminalServices-RDPClient/OperationalのイベントID 1029に記録されるユーザー名の解析方法について紹介しました。
それにしても Base64(SHA256(UserName)) という表記はトラップすぎますね…。
ということで、イベントID 1029のユーザー名を解析する際はご注意ください。

メモ帳(notepad.exe)の自動保存機能を調べてみた

概要

Windows 11では2023年9月頃にメモ帳(notepad.exe)に自動保存機能が追加された。
今回はたまたまメモ帳の自動保存先が分かったため、保存している内容を確認する方法 + メモ帳の挙動を調べてみた。

初回起動時のメッセージ

結論として、メモ帳が生成する特定のファイルを確認することで、自動保存している内容や、メモ帳が直近で開いていたファイルのフルパスや内容を簡単に確認可能なことが分かった。

自動保存されたメモの復元方法

まず、メモ帳を開くと以下のパスにbinファイルが生成される。(ファイル名はランダム?)

%localappdata%\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState 
(C:\Users\[ユーザ名]\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState)

binファイルの生成

ここで試しに文字を入力してみるが、入力するだけではbinファイルに内容は反映されない。
ちなみにbinファイルの中身はこんな感じ。

.bin生成時の中身

次に、文字を入力した状態でウィンドウを閉じてみる。(※タブではない)

この状態でbinファイルを開くと以下のような内容になっており、自動保存された内容が一部確認できる。

ウィンドウを閉じた(自動保存)後の.binの中身

ただ、この状態では日本語部分が読めないため、文字コードUTF-16に設定して開きなおしてみる。

UTF-16で開いた場合

すると、自動保存されたメモ内容の全文を確認することができた。
あとは本文前後の余計なバイナリを削除すると元の内容を復元できる。

メモ内容の復元

以上の方法で自動保存されたメモ内容を簡単に復元することができた。

binファイルの削除

binファイルはメモ帳のウィンドウを閉じても削除されないが、タブを閉じると削除される。

「タブを閉じる」をクリックすると以下のポップアップが表示される。

ここで「保存しない」をクリックするとbinファイルが削除される。

「保存しない」をクリックした後

ただウィンドウを閉じただけでは自動保存されるためbinファイルは消えないが、明確に「保存しない」を選択した際に削除されることが分かった。

すでに保存されているファイルを開いた場合

保存済みのファイルをメモ帳で開いた際もbinファイルが生成される。
たとえば、デスクトップに保存したテキストファイルを開くと以下のようになる。

デスクトップに保存したテキストファイルを開いた場合

binファイルの内容を確認すると、開いているファイルのフルパスが含まれていることが分かる。
ちなみに、テキストファイル以外であってもbinファイルが生成される。

画像をメモ帳で開いた場合

このbinファイルもウィンドウを閉じただけでは自動保存され、削除されない。
このことから、現在メモ帳に自動保存されている、直近で開いたファイルの履歴が分かる可能性がある。
ただし、こちらもメモ帳のタブを閉じるとbinファイルは削除される。

複数タブを開いた場合

複数のタブを開くと、1つのタブに対して1つのbinファイルが生成される。
たとえば、以下のように3つのタブを開き、それぞれに文字を入力してウィンドウを閉じてみる。

3つのタブに入力

すると、以下のように3つのbinファイルが確認できる。(.0.binと.1.binは除外)

各binファイルの中身は以下のようになっており、それぞれのメモ内容が記録されていることが分かる。

自動保存されないケース

下記のようなケースではメモ内容は自動保存されず、binファイルも生成されない。

  • タブを閉じるなどの操作で、「保存しない」をクリックした場合
  • メモ帳のウィンドウを複数開いている場合、2つ目以降に開いたウィンドウの内容は保存されない
    • ウィンドウを閉じる際に保存する・しないのポップアップが表示される
  • メモ帳の設定画面にて、「メモ帳の起動時」を「新しいウィンドウを開く」に設定している場合
    • この場合も保存する・しないのポップアップが表示される
    • デフォルトでは「前のセッションからコンテンツを開く」になっていると思われる

メモ帳の設定画面

.0.binと.1.binについて

メモ内容が保存されているbinファイルと同名の.0.binと.1.binは以下のタイミングで生成される。

  1. メモ帳を開き、自動保存されている内容を表示する(もしくは保存しているファイルを開く)
  2. メモ内容を編集せず、そのままメモ帳を閉じる
  3. ウィンドウを閉じると.0.binと.1.binが生成される

逆に、メモ内容を編集してウィンドウを閉じるとこれらのファイルは生成されず、すでに存在している場合は削除される。

追記した例

メモ内容をいじって閉じると.0.binと.1.binは消える

ちなみに、.0.binと.1.binの中身が何なのかは不明…

まとめ

現在のWindows 11のメモ帳はデフォルトではメモ内容を自動保存するようになっており、保存されている内容を簡単に確認可能なことが分かった。また、最近メモ帳で開いたファイルの痕跡も残っている可能性があるため、フォレンジックの際に役立つかもしれない…。
メモ帳の自動保存を無効にしたい場合は設定を「新しいウィンドウを開く」にしておきましょう。

参考サイト