Skip to content

Recovering Deleted Windows Notifications

Why dismissed and expired toasts survive in wpndatabase.db, where SQLite leaves them (freeblocks, freelist, WAL frames) and how to recover them without fooling yourself.

Published on 4 min read

TL;DR. Deleting a row in SQLite marks its space as free; the bytes stay until reused (unless secure_delete is on). In wpndatabase.db that means dismissed and expired notifications — including chat previews — can often be recovered from free space in pages, the freelist and older page images in the file and the WAL. Treat each recovered row as a lead, not a record.

Where deleted rows hide

The SQLite file format gives four places:

  1. Freeblocks. A deleted cell's space becomes a freeblock in the same b-tree page. Only its first four bytes are overwritten (the pointer to the next freeblock and the block size); the rest of the record — including the payload — is intact.
  2. The freelist. When a page no longer holds any row, it goes on the freelist, content and all, until SQLite needs a page again.
  3. Superseded pages in the database file. With the WAL present, the file holds the page as it was at the last checkpoint. If a later transaction deleted a row, the old page image still contains it.
  4. WAL frames. The WAL holds successive versions of pages. Older frames, and frames of a previous WAL generation, can carry rows that newer versions no longer have.

A notification database is a good candidate: payloads are a few hundred bytes of distinctive XML, rows churn constantly, and the file is rarely vacuumed.

How the parser recovers them

The Windows Notification Parser uses two methods:

  • Deleted by the WAL. Rows present in the checkpointed database but gone once the WAL is applied are exact: the parser reads them with the normal record decoder and labels them "deleted by a transaction in the WAL".
  • Payload-anchored carving. Every <toast, <tile, <badge or <?xml found in any page or WAL frame is a candidate start of a Payload value. For each, the parser looks back for a record header whose serial types match the live Notification schema and place the payload exactly at that offset. Column types, a plausible FILETIME and an ASCII Type must also check out. Payloads that spilled onto overflow pages are reassembled from the chain when it is still there; otherwise the row is marked partial.

Rows identical to a live row are dropped. A carved row with the same Id as a live one but different content is shown as an earlier version.

Reading a recovered row

Each recovered notification says where it came from, for example free space in page 4, freelist page 12 or WAL frame 3 (page 5). Check three things before you use it:

  • Is it complete? A partial row has a cut payload or missing fields.
  • Does its handler still exist? HandlerId is resolved against the live handler table; a missing handler is possible.
  • Does it fit the timeline? An arrival time in the middle of other activity is a good sign; a time far outside the data deserves suspicion.

Limits

  • Space is reused as new notifications arrive: recovery gets worse with time since deletion.
  • VACUUM rewrites the file and drops free space.
  • If a build enabled secure_delete, freed content is zeroed. Whether any Windows build does this is not documented; an empty result is not proof of absence.
  • A carved record whose header was overwritten cannot be recovered by this method. Other carving approaches exist; this one favours few false positives.

FAQ

Can deleted Windows notifications be recovered?

Often, until SQLite reuses the space. A deleted row stays in the page as a freeblock, whole pages go to the freelist, and older page images remain in the database file and the WAL. Recovered rows are best-effort evidence and should be labelled as such.

Does opening the database destroy deleted records?

It can. Opening a WAL database with SQLite and closing it may checkpoint and reset the WAL, and writing to it reuses free space. Work on hashed copies.

Related articles

Field reference for the Windows notification database: Notification, NotificationHandler, HandlerAssets, WNSPushChannel, FILETIME times and the toast XML payload.

What the Windows notification database records, where it lives, what it proves and where it stops: a practical guide to wpndatabase.db for DFIR.

A fictional walkthrough: what one user's notification database adds to an intrusion timeline — a download, an antivirus alert, a new app and a deleted chat message.