Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ $RECYCLE.BIN/
?LEAPP_Reports_*
path_list.txt
coordinates.db
.lab-output/

# Documentation
docs/_build/
Expand Down
55 changes: 55 additions & 0 deletions admin/docs/WLEAPP_TO_DLEAPP_AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# WLEAPP to DLEAPP modernization audit

Authors: `@AlexisBrignoni, Codex`

This audit compares the 15 artifact modules in the local WLEAPP repository at
commit `7690aa4` with observations from the controlled Windows VM corpus. It is
an implementation and test-status record, not a claim that an artifact is
absent from Windows generally.

The lab observation applies to a Parallels ARM virtual machine running Windows
build `26200.8457`, display version `25H2`. The Windows registry value collected
as `ProductName` says `Windows 10 Pro`; the build, display version, architecture,
and captured UI are therefore reported separately rather than treating that
single registry label as the operating-system name.

## Migrated and corpus-validated

| WLEAPP module | DLEAPP artifact | Corpus result | Modernization value |
|---|---|---:|---|
| `activitiesCache.py` | ActivitiesCache | 5 rows | Preserves non-JSON payloads that the predecessor discarded, exposes application identifiers, and reports Unix timestamps in UTC with the primary start time first. |
| `windowsNotification.py` | Notifications | 3 rows | Adds handler identity, payload type, extracted text, payload size and SHA-256, retains the raw payload, and reports FILETIME values in UTC. The controlled toast token was recovered. |
| `windowsStickyNotes.py` | Sticky Notes | 2 rows | Retains empty notes with metadata, removes the internal text marker, adds note identifiers and window state, and reports updated/created/deleted .NET-tick times first in UTC. The controlled note token was recovered. |
| `setupapiDev.py` | SetupAPI Sections | 1 row | Parses complete SetupAPI sections rather than assuming every timestamp is a device's first connection. Times are labeled device-local because the log does not record a UTC offset. |

The focused profile is `windows-system.dlprofile`.

## Retest when a representative artifact is available

| WLEAPP module | Lab observation | Required next evidence |
|---|---|---|
| `betterDiscord.py` | BetterDiscord MessageLoggerV2 data was not present. | A consented test profile with that specific third-party plugin and known messages. |
| `box.py` | Box databases were not present. | Current Box Drive installation, app version, and controlled local/cloud file actions. |
| `dropbox.py` | Dropbox databases were not present. | Current Dropbox installation and controlled sync/history actions. |
| `googleDrive.py` | DriveFS metadata database was not present. | Current Google Drive for desktop installation and controlled sync actions. |
| `pfirewall.py` | `pfirewall.log` was absent. | A separately approved test that enables firewall logging, records its policy state, and produces known allowed/blocked traffic. |
| `windowsAlarms.py` | Clock `11.2605.10.0` requested an update. `settings.dat` existed, but no controlled alarm could be created. The WLEAPP parser contains a structure TODO and requires `pyregf`, which DLEAPP does not currently require. | A usable Clock build, known alarms, the JSON/registry-store variants, and dependency review. |
| `windowsEdge.py` | `WebCacheV01.dat` existed but was live-locked. Its evidentiary scope is legacy Edge/Internet Explorer rather than current Chromium Edge. | An offline byte-for-byte copy and known legacy-WebCache activity. Do not present it as current Edge browsing history. |
| `windowsPhotos.py` | Photos `2026.11020.20001.0` was present, but the WLEAPP target `MediaDb.v1.sqlite` was not found after a known image was placed in Pictures and Photos was opened. | Storage discovery and schema research for this Photos version before porting the old query. |
| `windowsYourPhone.py` | Current Phone Link and CrossDevice packages were installed, but the targeted databases were not present in the unpaired profile. | A dedicated synthetic phone/account pairing. Personal accounts or devices should not be used merely to obtain parser coverage. |

## Legacy candidates

| WLEAPP module | Reason to avoid a blind port |
|---|---|
| `facebookMessenger.py` | The parser targets the retired Facebook Messenger UWP storage layout, and no matching package or database was present. Preserve it only with a representative legacy corpus. |
| `windowsCortana.py` | The parser targets legacy Cortana `DeviceSearchCache` text files, and no matching package or files were present. Preserve it only with a representative legacy corpus. |

## Acquisition note

ActivitiesCache and Notifications could not be copied byte-for-byte while the
Windows user session was active. Their raw-copy failures and error messages are
retained in the collection manifest. For parser testing only, the SQLite backup
API produced read-only-source logical snapshots containing committed WAL data;
each snapshot passed `PRAGMA quick_check` and was hashed. Those snapshots are
examiner-derived and must not be described as original acquired files.
161 changes: 161 additions & 0 deletions admin/test/scripts/test_windows_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""Tests for the Windows system artifacts migrated from WLEAPP."""

# pylint: disable=protected-access

import sqlite3
from datetime import datetime, timezone

from scripts.artifacts import windowsSystem


class _Context:
def __init__(self, files):
self._files = files

def get_files_found(self):
return self._files

@staticmethod
def get_relative_path(path):
return str(path)


def test_windows_timestamp_epochs():
expected = datetime(1970, 1, 1, tzinfo=timezone.utc)
assert windowsSystem._utc_from_unix_seconds(1) == expected.replace(second=1)
assert windowsSystem._utc_from_filetime(116444736010000000) == expected.replace(
second=1
)
assert windowsSystem._utc_from_dotnet_ticks(621355968010000000) == (
expected.replace(second=1)
)
assert windowsSystem._utc_from_filetime(0) == ""


def test_activities_cache_retains_non_json_payload(tmp_path):
database_path = tmp_path / "ActivitiesCache.db"
with sqlite3.connect(database_path) as database:
database.execute(
"""
CREATE TABLE Activity (
StartTime, EndTime, LastModifiedTime, ExpirationTime,
LastModifiedOnClient, AppActivityId, AppId, Payload,
ActivityType, ActivityStatus, Tag, "Group", IsLocalOnly, IsRead
)
"""
)
database.execute(
"""
INSERT INTO Activity VALUES (
1, 2, 3, 4, 5, 'activity-id',
'[{"application":"test.application"}]', 'Tk9OLUpTT04=',
11, 1, 'tag', 'group', 1, 0
)
"""
)

_, rows, _ = windowsSystem.activitiesCache.__wrapped__(
_Context([database_path])
)
assert len(rows) == 1
assert rows[0][5] == "activity-id"
assert rows[0][6] == "test.application"
assert rows[0][16] == "Tk9OLUpTT04="


def test_notifications_include_handler_and_payload_hash(tmp_path):
database_path = tmp_path / "wpndatabase.db"
with sqlite3.connect(database_path) as database:
database.execute(
"""
CREATE TABLE NotificationHandler (
RecordId INTEGER, PrimaryId TEXT, HandlerType TEXT,
CreatedTime TEXT, ModifiedTime TEXT
)
"""
)
database.execute(
"""
CREATE TABLE Notification (
ArrivalTime, ExpiryTime, BootId, Id, HandlerId, Type,
PayloadType, Payload, Tag, "Group", ExpiresOnReboot
)
"""
)
database.execute(
"INSERT INTO NotificationHandler VALUES "
"(7, 'test.handler', 'app:test', 'created', 'modified')"
)
database.execute(
"""
INSERT INTO Notification VALUES (
116444736010000000, 116444736020000000,
116444736000000000, 9, 7, 'toast', 'Xml',
'<toast><text>DLEAPP-NOTIFICATION-TEST-001</text></toast>',
'tag', 'group', 1
)
"""
)

_, rows, _ = windowsSystem.windowsNotifications.__wrapped__(
_Context([database_path])
)
assert len(rows) == 1
assert rows[0][7] == "test.handler"
assert rows[0][11] == "DLEAPP-NOTIFICATION-TEST-001"
assert len(rows[0][16]) == 64


def test_sticky_notes_timestamp_first_and_markup_removed(tmp_path):
database_path = tmp_path / "plum.sqlite"
with sqlite3.connect(database_path) as database:
database.execute(
"""
CREATE TABLE Note (
UpdatedAt, CreatedAt, DeletedAt, Id, ParentId, Text,
IsOpen, IsAlwaysOnTop, Theme, WindowPosition
)
"""
)
database.execute(
"""
INSERT INTO Note VALUES (
621355968020000000, 621355968010000000, NULL,
'note-id', 'parent-id',
'\\id=01234567-89ab-cdef-0123-456789abcdef known text',
1, 0, 'Yellow', 'ManagedPosition='
)
"""
)

headers, rows, _ = windowsSystem.windowsStickyNotes.__wrapped__(
_Context([database_path])
)
assert headers[:3] == (
("Updated Time (UTC)", "datetime"),
("Created Time (UTC)", "datetime"),
("Deleted Time (UTC)", "datetime"),
)
assert rows[0][5] == "known text"


def test_setupapi_sections_do_not_claim_first_connection(tmp_path):
log_path = tmp_path / "setupapi.dev.log"
log_path.write_text(
"""
>>> [Device Install (Hardware initiated) - USB\\VID_1234&PID_5678\\ABC]
>>> Section start 2026/07/29 10:00:00.100
dvi: test
<<< Section end 2026/07/29 10:00:01.600
<<< [Exit status: SUCCESS]
""".lstrip(),
encoding="utf-8",
)
headers, rows, _ = windowsSystem.setupapiSections.__wrapped__(
_Context([log_path])
)
assert headers[0] == "Start Time (device local)"
assert len(rows) == 1
assert rows[0][3] == r"USB\VID_1234&PID_5678\ABC"
assert rows[0][4] == "SUCCESS"
assert rows[0][5] == 1.5
139 changes: 139 additions & 0 deletions admin/windows_lab/DLEAPPLab.Common.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# DLEAPP Windows corpus laboratory helpers.
# Authors: @AlexisBrignoni, Codex

Set-StrictMode -Version 2.0

function Get-DLEAPPArtifactDefinitions {
$local = $env:LOCALAPPDATA
$roaming = $env:APPDATA
$windows = $env:SystemRoot

@(
[pscustomobject]@{
Artifact = "ActivitiesCache"
Patterns = @("$local\ConnectedDevicesPlatform\*\ActivitiesCache.db*")
}
[pscustomobject]@{
Artifact = "BetterDiscord Message Logger"
Patterns = @(
"$roaming\BetterDiscord\plugins\MessageLoggerV2Data.config.json"
)
}
[pscustomobject]@{
Artifact = "Box Drive"
Patterns = @("$local\Box\Box\Data\*.db*")
}
[pscustomobject]@{
Artifact = "Dropbox"
Patterns = @(
"$local\Packages\*DROPBOX*\LocalState\users\*\*.sqlite*",
"$local\Dropbox\instance*\sync_history.db*"
)
}
[pscustomobject]@{
Artifact = "Facebook Messenger (Legacy)"
Patterns = @("$local\Packages\FACEBOOK.*\AC\Messenger\msys_*.db*")
}
[pscustomobject]@{
Artifact = "Google Drive"
Patterns = @("$local\Google\DriveFS\*\metadata_sqlite_db*")
}
[pscustomobject]@{
Artifact = "Windows Firewall"
Patterns = @("$windows\System32\LogFiles\Firewall\pfirewall.log")
}
[pscustomobject]@{
Artifact = "SetupAPI Device Installation"
Patterns = @("$windows\INF\setupapi.dev.log")
}
[pscustomobject]@{
Artifact = "Windows Clock and Alarms"
Patterns = @(
"$local\Packages\Microsoft.WindowsAlarms_*\LocalState\Alarms\Alarms.json",
"$local\Packages\Microsoft.WindowsAlarms_*\Settings\settings.dat"
)
}
[pscustomobject]@{
Artifact = "Cortana DeviceSearchCache (Legacy)"
Patterns = @(
"$local\Packages\Microsoft.Windows.Cortana_*\LocalState\DeviceSearchCache\AppCache*.txt"
)
}
[pscustomobject]@{
Artifact = "Microsoft Edge Legacy"
Patterns = @("$local\Microsoft\Windows\WebCache\WebCacheV01.dat*")
}
[pscustomobject]@{
Artifact = "Windows Notifications"
Patterns = @(
"$local\Microsoft\Windows\Notifications\wpndatabase.db*"
)
}
[pscustomobject]@{
Artifact = "Windows Photos"
Patterns = @(
"$local\Packages\Microsoft.Windows.Photos_*\LocalState\MediaDb*.sqlite*"
)
}
[pscustomobject]@{
Artifact = "Windows Sticky Notes"
Patterns = @(
"$local\Packages\Microsoft.MicrosoftStickyNotes_*\LocalState\plum.sqlite*"
)
}
[pscustomobject]@{
Artifact = "Phone Link"
Patterns = @(
"$local\Packages\Microsoft.YourPhone_*\LocalCache\Indexed\*\System\Database\*",
"$local\Packages\MicrosoftWindows.CrossDevice_*\LocalState\*"
)
}
)
}

function Get-DLEAPPTargetFiles {
$seen = @{}
foreach ($definition in Get-DLEAPPArtifactDefinitions) {
foreach ($pattern in $definition.Patterns) {
$matches = @(Get-ChildItem -Path $pattern -Force -File -ErrorAction SilentlyContinue)
foreach ($file in $matches) {
$key = $file.FullName.ToLowerInvariant()
if ($seen.ContainsKey($key)) {
continue
}
$seen[$key] = $true
[pscustomobject]@{
Artifact = $definition.Artifact
Pattern = $pattern
File = $file
}
}
}
}
}

function New-DLEAPPDirectory {
param([Parameter(Mandatory = $true)][string]$Path)

if (-not (Test-Path -LiteralPath $Path)) {
New-Item -ItemType Directory -Path $Path -Force | Out-Null
}
}

function ConvertTo-DLEAPPTsvValue {
param([AllowNull()][object]$Value)

if ($null -eq $Value) {
return ""
}
([string]$Value).Replace("`t", " ").Replace("`r", " ").Replace("`n", " ")
}

function Get-DLEAPPRelativeCollectionPath {
param([Parameter(Mandatory = $true)][System.IO.FileInfo]$File)

$driveName = $File.PSDrive.Name
$driveRoot = $File.PSDrive.Root
$relative = $File.FullName.Substring($driveRoot.Length).TrimStart("\")
Join-Path $driveName $relative
}
Loading
Loading