# Windows file permissions and handles

These experiments show how changing a file's permissions affects existing
handles and new opens. There are six PowerShell experiments and a C++ program
that adds a test with a restricted impersonation token.

## Get started

Use a Windows 11 lab VM with PowerShell 5.1 or later. Extract the lab ZIP and
open PowerShell in that folder, **not as administrator**. PowerShell compiles
the included C# wrapper; you don't need to install a compiler for experiments
1-6.

Run setup:

```powershell
.\Setup-Lab.ps1
```

Setup downloads Procmon from Microsoft and checks its signature. It stores the
lab under `%LOCALAPPDATA%\WindowsHandleLab`.

Find the line starting `Procmon:` in the setup output. Open that folder,
right-click the Procmon executable, and choose **Run as administrator**. Review
its licence if prompted. If capture starts automatically, stop it with Ctrl+E.

Back in the normal PowerShell window, run:

```powershell
.\Invoke-HandleLab.ps1 -Experiment 1 -Pause
```

The runner prints its PID, run folder, and results file, then waits for Enter.
In Procmon, open Filter > Filter, reset the existing filters, and add these two
Include rules:

| Column | Condition | Value | Action |
| --- | --- | --- | --- |
| PID | is | The printed PID | Include |
| Path | begins with | The printed run folder | Include |

Start capture with Ctrl+E, then press Enter in PowerShell. At the next pause,
permissions have changed and the writer is still open. Inspect the ACL in a
second PowerShell window:

```powershell
icacls.exe '<file path printed at the checkpoint>'
```

Press Enter in the lab window to finish the experiment. The old handle should
write successfully, the new writable open should fail with error 5, and the
runner should report `passed: True; DACL restored: True`.

Stop Procmon capture. Save the displayed events as a PML file and keep it with
the run's JSON report. Repeat with a fresh PID and folder filter for each run.

## Experiments

Change the number after `-Experiment` to choose another test:

| Number | Test | Expected result |
| --- | --- | --- |
| 1 | Remove write permission after opening a writer | The old handle writes; a new writer is denied |
| 2 | Add write permission after opening a reader | The old handle still cannot write; a new writer succeeds |
| 3 | Remove data access, then write a new marker | The old reader sees the new bytes; a new reader is denied |
| 4 | Duplicate a handle after changing permissions | Same-access copy writes; read-only copy reads, but cannot write or upgrade to read/write |
| 5 | Open a file with share mode zero | Another data open fails with error 32, then succeeds after the exclusive handle closes |
| 6 | Keep a read-only mapped view after closing the file handle | The view can still be read; a new reader is denied |
| 7, C++ | Impersonate a restricted token | The old handle works; a new open fails during impersonation and succeeds after reversion |

Select several experiments, or run all six without pauses:

```powershell
.\Invoke-HandleLab.ps1 -Experiment 2,3 -Pause
.\Invoke-HandleLab.ps1
```

Each invocation creates a new run folder and report.

## Results

The lab's `data` folder holds files and DACL backups, `tools` holds Procmon,
and `output` holds reports and setup's `environment.json`.

The JSON report records API results, errors, handle grants, returned bytes,
file identities, and cleanup. Check it with:

```powershell
$report = Get-Content '<results file printed by the runner>' -Raw | ConvertFrom-Json
$report.Results | Select-Object Experiment, Passed, RestorationSucceeded
```

`GrantedAccess` comes from `NtQueryObject` and stays empty if the query fails.
`RequestedAccess` records what the caller asked for.

Use the report alongside Procmon: a rejected API call may have no filesystem
event, and experiment 6 reads mapped memory without calling `ReadFile`.

Each experiment creates a file with `CREATE_NEW`, saves its DACL, and restores
the DACL and inheritance setting on cleanup. Parent permissions, ownership,
and the SACL are left alone. Control rights remain available for recovery.

## C++ experiment

The native program needs a C++17 compiler and Windows headers. For
GCC/MinGW-w64, used in the recorded runs:

```powershell
g++ -std=c++17 -Wall -Wextra -Wpedantic -Werror -static -municode .\HandleLab.cpp -o HandleLab.exe -ladvapi32
```

With Visual Studio's C++ build tools and Windows SDK, use a Developer
PowerShell. This MSVC command has not been tested:

```powershell
cl /std:c++17 /EHsc /W4 .\HandleLab.cpp /Fe:HandleLab.exe /link Advapi32.lib
```

Run the file-permissions and impersonation tests:

```powershell
.\HandleLab.exe --impersonation --pause
```

Omit `--impersonation` to run only the file-permissions test. The program prints
its PID and file path before the first pause; use them for your Procmon filter.
It creates a file under `%LOCALAPPDATA%\WindowsHandleLab\data` and restores its
DACL on completion.

It impersonates a restricted copy of its own token, then reverts before DACL
cleanup. No second account is needed.

## Setup options

Setup reuses verified tools in its own `tools` folder. It does not search
other installations.

| Switch | Use it when |
| --- | --- |
| `-SkipToolDownloads` | You already have Procmon, or want to run without a trace |
| `-IncludeProcessExplorer` | You also want a portable copy of Process Explorer |
| `-RefreshTools` | You want fresh downloads of the selected tools |

For a custom folder, use the same `-LabRoot` on setup, the PowerShell runner,
and recovery. Choose local NTFS storage; the runner rejects junctions and other
paths redirected through reparse points.

## Recovery

Cleanup runs on completion and handled errors, but a crash can interrupt it.
Each file's original DACL is saved beside it as
`<filename>.original-dacl.sddl` before permissions change.

From the extracted folder, use the original lab account in a normal PowerShell
window:

```powershell
.\Restore-HandleLab.ps1 -File '<full path of the interrupted experiment file>'
```

Supply `-LabRoot` if you chose a custom folder. Recovery checks the file and
backup are in that lab's `data` folder, restores the DACL, and verifies it.
Ownership and the SACL are preserved.

Files are left in place. Keep the records you need, then remove disposable runs.

## Troubleshooting

For a downloaded script blocked as unsigned, review the scripts, then use
[`Unblock-File`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/unblock-file)
to remove their download mark:

```powershell
Unblock-File -LiteralPath .\Setup-Lab.ps1, .\Invoke-HandleLab.ps1, .\Restore-HandleLab.ps1
```

Execution policy is unchanged. Requirements for signed scripts or restrictions
on scripting still apply.

| Problem | What to check |
| --- | --- |
| Procmon download or signature check fails | Read the warning and rerun setup, or use `-SkipToolDownloads` with your own copy. API tests can run without Procmon. |
| The lab rejects an elevated console | Open a normal PowerShell window. Only Procmon needs elevation. |
| `Add-Type` or language-mode error | Use a lab VM that permits `Add-Type` in FullLanguage mode. |
| C# source differs from the loaded type | Open a fresh PowerShell after editing `HandleLab.cs`. |
| Run fails or cleanup is interrupted | Keep the files and inspect `Failure` in the report. Use recovery if the DACL was not restored. |

## Files and tested versions

The ZIP contains the three `.ps1` scripts, `HandleLab.cs`, `HandleLab.cpp`, and
this README. The diagram and prediction component belong to the blog page.

All seven experiments were recorded on Windows 11 Business build 26200.9457,
x64, with Procmon 4.11. The PowerShell captures used version 7.6.5; the six
PowerShell tests also passed in Windows PowerShell 5.1. The C++ build used
GCC/MinGW-w64 16.2.0 with warnings treated as errors. Recovery was tested
separately. Experiments 1 and 7 were repeated successfully on build 26200.9711
for the terminal screenshots.

These tests cover ordinary local NTFS files. MSVC, ARM64, the optional WinDbg
and worker-thread exercises, a separate reader VM, and SMB access have not been
tested. The post links a separate evidence ZIP containing the recorded results.
