0%

SandboxIE源码分析

1.内核回调

SbieDrv.sys SbieDll.dll

目录结构:

Apps:窗口UI相关

Com:对于一些com组件的隔离

SandBoxBITS: 后台智能传输服务的隔离

SandBoxCrypto: 认证服务的隔离

SandBoxRpcSs:rpc 服务的隔离

SandBoxWUAU: 更新服务的隔离

SboxDcomLaunch:Dcom 服务的隔离

Core:核心功能实现

SboxDrv: 注册一些回调,隔离的控制,权限控制,注入,通信..

SboxHostDll: 注入器

SboxDll: 应用层注入Dll

Common:

Parse:

SboxMsg:

维护了一个沙箱程序链表proc

PsSetCreateProcessNotifyRoutine 进程创建回调:

判断是否应作为沙箱进程,防止沙箱逃逸.Dll的注入

判断沙箱主进程是否初始化完毕,未初始化完成,不做处理.

传入参数:分配的进程id,创建该进程的父进程id,创建该进程的进程id

CreateInfo->ParentProcessId, PsGetCurrentProcessId()都要传,因为通过RtlCreateUserProcess父进程是可以指定的.

会加大溯源难度,这里一定要传一个PsGetCurrentProcessId(),需要注意的是在回调中PsGetCurrentProcessId()存储的是真实的父进程id,后续进行正确的判断,否则脱离沙箱管控,会带来沙箱逃逸问题..

获取进程完整路径

判断ParentId是否由沙箱所创建,如果是,将标志位add_process_to_job(由沙箱管理的进程标识)修改为true.

如果不是,则继续判断PsGetCurrentProcessId()获取的id是否是一个由沙箱管控的进程.

如果不是,则继续判断是否为一个强制沙箱模式运行的进程.

强制进程:设置中进行配置,某些进程强制在沙箱下运行.,

默认配置为:iexplore.exe firefox.exe App*.exe App?.exe outlook.exe cl?cke?.exe

如果是一个force_process,则通过函数Process_GetForcedStartBox**在沙盒下运行,add_process_to_job修改为true.

代码的注入

当检查通过后,确定是一个应被沙盒化的进程

调用Process_Low_Inject进行dll的注入.,它这里的注入是通知ring3应用程序进行了跨进程的一个远程线程注入.由于session0隔离限制,

某些进程是无法这样注入的,虽然可以绕过.

这部分逻辑其实可以在ring0实现.通过挂靠进程,然后向ZwTestAlert函数头部写入shellcode完成dll的加载

PsSetLoadImageNotifyRoutine 模块加载回调

沙盒空间的初始化

跳过系统映像,通过判断processId是否为0和ImageInfo中的SystemModeImage字段.

如果加载了32位ntdll,会将32位ntdll的基址记录于proc->ntdll32_base

创建沙盒空间,沙盒的初始化相关操作.文件,注册表..

ObRegisterCallbacks 对象回调

主要要做的工作是句柄沙箱进程与主机进程之间句柄权限的管理.

对于内核句柄,不做操作.

对于进程句柄,线程句柄的处理:

​ 如果打开的进程句柄的进程id,是当前进程,忽略.

​ 如果打开句柄的操作不来自沙箱,但是主机进程的目标是沙盒进程

​ 将句柄修改为无权限. PROCESS_DENIED_ACCESS_MASK**或者THREAD_DENIED_ACCESS_MASK

​ 如果沙箱不是机密的,则会放出一些进程的访问权限,csrss.exe conhost.exe taskmgr.exe sandman.exe.

​ 它这里实现的较为简单,如果将shellcode注入到这些进程里面,可以很简单的绕过.

​ 如果打开句柄的操作来自沙箱进程,目标也是沙箱进程,允许.

​ 如果访问的是外部主机进程,则通过用户配置判断是否将句柄修改为无权限.并记录跨沙箱访问记录.

2.服务

1.scm.c

Scm_Init_AdvApi

hook安装

执行Scm_Notify_Init 安装NotifyServiceStatusChange回调

执行Scm_HookRegisterServiceCtrlHandler

Scm_Notify_Init

NotifyServiceStatusChange此回调在vista和更高版本中可用.

所以在高版本下,sbie hook了此函数.

Scm_HookRegisterServiceCtrlHandler

内部声明了2个7字节的opcode,

64位下7字节覆盖掉了原代码.

image-20221019100811727

其他情况默认hook.

Scm_OpenSCManagerW

对当前线程token进行检查,如果没有查询到token,并且当前进程标识是受限制的,则无权限.这里这个标识为默认是可以不受限的.

获取到token,检查此token是否在受限制的组中.通过函数NtQueryInformationToken 获取到的受限sid列表如果大于0则直接返回无权限.

这个函数实际返回的是自定义的虚拟句柄,自定义的返回值

#define HANDLE_SERVICE_MANAGER ((SC_HANDLE)0x12340001)
#define HANDLE_SERVICE_STATUS ((SERVICE_STATUS_HANDLE)0x12340003)
#define HANDLE_SERVICE_LOCK ((SC_LOCK)0x12340005)
#define HANDLE_EVENT_LOG ((SC_LOCK)0x12340007)

Scm_OpenServiceW

判断通过Scm_OpenSCManagerW获取到的handler值是否为HANDLE_SERVICE_MANAGER,如果不是,返回无权限.而后进行了一些参数判断.

检查此服务是否在沙盒中

直接调用函数CreateKey返回句柄.

如果不在沙盒中.

向sbiesvc通信查询该服务具体信息,不从本进程查询,可以确定是无法确定当前沙盒进程的权限,有些api无法直接使用,所以借助sbiesvc调用函数查询服务信息.如果查询得到信息,构造一个serviceName查询不到,返回0.对服务名做了一个拼接处理sbox+服务名,之后作为句柄直接返回.

image-20221019110826671

Scm_CloseServiceHandle

这里关闭句柄做了判断,自己构造的句柄,并针对回调相关做了其他的处理,后面说明,直接释放字符串.

2.scm_create.c

Scm_LockServiceDatabase

返回了虚拟的句柄HANDLE_SERVICE_LOCK

Scm_UnlockServiceDatabase

简单的处理,判断句柄是否为HANDLE_SERVICE_LOCK

Scm_CreateServiceW

由于对注册表进行了重定向,沙箱内修改注册表数据不会影响宿主机.

通过Scm_OpenService,判断该服务是否已存在.如果存在直接返回

调用windows原始api,在系统注册表中创建该服务的键.并设置各项键值

之后将该服务名添加入sbiesvc沙箱服务列表中.

Scm_ChangeServiceConfigW

验证该服务是否存在,该服务是否是沙箱的服务.

如果不是并且判断服务前17个字符是否为”clr_optimization_”,如果是,这是程序安装时使用的.net服务,不需要做任何处理,依然会成功.

如果不是沙箱服务

如果不是并且判断服务前17个字符是否为”clr_optimization_”,如果是,这是程序安装时使用的.net服务,不需要做任何处理,依然会成功.

sbie的处理是一律返回无权限.这里控制沙箱内的进程无法修改外部服务配置.

如果是沙箱的服务

调用api获取句柄,调用原始api处理,修改配置.

Scm_DeleteService

如果不是沙箱的服务,返回无权限.

如果是沙箱的服务

获取sbiesvc句柄和要删除的服务句柄.

调用删除注册表中记录

在sbiesvb记录的沙盒列表中删除该记录.

SbieDll_StartBoxedService

服务的启动.

SbieRpcSs服务可以将启动的服务添加入沙盒中.

对参数进行检查,判断该服务是否可以启动.

修改服务状态,初始化服务

对sandboxie服务做特殊处理.

bits服务 WUAU服务 Crypto服务

通过clsid删除即将启动服务的appid中的LaunchPermission和AccessPermission

为什么要删除这个,msdn描述这2个值会决定服务的正常启动

关于LaunchPermission的描述

这是一个REG_BINARY值。在收到本地或远程请求启动该类的服务器时,在模拟客户端时检查该值所描述的ACL,允许或不允许成功启动服务器。如果这个值不存在,DefaultLaunchPermission值会以同样的方式被检查,以确定该类代码是否可以被启动。

关于AccessPermission的描述

这是一个REG_BINARY值。它包含描述可以访问该类实例的委托人的访问控制列表(ACL)的数据。在收到连接到该类现有对象的请求时,被调用的应用程序会在模拟调用者时检查ACL。如果访问检查失败,连接将被拒绝。如果这个命名的值不存在,DefaultAccessPermission ACL将被测试以决定是否允许连接。

对于不调用CoInitializeSecurity或不使用IGlobalOptions接口来指定AppID的应用程序,必须将应用程序的二进制文件的可执行文件映射到AppID中描述的应用程序的AppID。这是必需的,以便 COM 可以定位应用程序的 AppID。

而后调用函数SbieDll_RunFromHome,内部使用CreateProcess启动,启动前,使用函数

AdvApi_EnableDisableSRP hook了SaferComputeTokenFromLevel,确保CreateProcess的正常执行,为什么会hook这个函数呢

CreateProcess中会调用SaferComputeTokenFromLevel进行一个软件限制策略的检查,SaferComputeTokenFromLevel的返回值决定了是失败还是继续执行.hook后的函数,会返回true

image-20221019171942165

执行后,关闭了hook.

如果上述方式未执行成功,就通过sbiesvc启动,sbiesvc启动前,涉及到了对token的处理

Scm_StartServiceCtrlDispatherX

与服务管理器连接,这里直接创建线程执行了serviceMain函数.让服务启动.

Scm_RegisterServiceCtrlHandlerA

记录处理函数,和参数的指针,这里定义了全局变量.

3.scm_event

主要是日志相关,sbie没做任何处理.

4.scm_misc

主要是hook相关

Scm_SecHostDll

兼容性处理

win8下可能获取不到sechost.dll的模块加载基址.如果获取不到,设置标志位.

win7下做特殊处理,hook advapi32中的一些函数,并将sechost中的对应函数hook至advapi32

这个函数在启动服务.

5.Scm_SetupMsiHooks

Scm_SetupMsiHooks

这里对msi server进行了权限的处理,hook掉了OpenProcessToken产生的句柄,然后hook GetTokenInformation,在获取这个句柄的信息时,返回系统sid.然后在NtClose清除令牌,防止被使用.

但是这段代码被取消了.

Scm_Notify_Init

hook

Scm_NotifyServiceStatusChangeW

内部调用Scm_Notify_ThreadProc

查询服务信息,插入用户apc,执行原回调函数

6.scm_query

Scm_QueryServiceConfigW

Scm_QueryServiceByHandle->Scm_GetHandleName->Scm_QueryServiceByName

如果是沙箱服务,自己调api查询

如果是宿主机服务,交由sbiesvc查询.

如果是在64位下运行的32位进程,sbiesvc会返回一个32位的结构.这里要处理下.

EnumServicesStatusA

枚举当前系统服务

参数检查.

调用sbiesvc查询系统服务列表.

获取沙箱服务列表.

查询服务状态信息,筛选掉一些服务,类型不匹配的,获取停止的.

调用函数查询配置信息.

Scm_GetServiceDisplayNameW

调函数,查信息.

Scm_GetServiceKeyNameW

调函数,获取全部服务,遍历对比lpDisplayName信息,匹配,获取keyname.

Scm_EnumDependentServicesA

未做特殊处理,不知道会不会有兼容性问题.

Scm_QueryServiceObjectSecurity

此函数作用是检索与服务对象关联的安全描述符,返回的安全描述符是sbie自己构造的.

sbie构造的sid和dacl.可以知道sbie对安全描述符这一块也没做hook相关处理.

image-20221024140608559

Scm_SetServiceObjectSecurity

未做处理.

3.IPC和RPC

函数原型

.函数原型:

NTSTATUS

NTAPI

NtCreatePort(

OUT PHANDLE PortHandle,

IN POBJECT_ATTRIBUTES ObjectAttributes,

IN ULONG MaxConnectionInfoLength,

IN ULONG MaxMessageLength,

IN ULONG MaxPoolUsage

);

功能描述:

创建一个LPC port对象。LPC port的创建者即是LPC通信的服务端(服务端)

参数:

PortHandle 输出 PHANDLE是一个指针变量,如果函数被成功调用则该变量指向port对象

ObjectAttributes 输入 POBJECT_ATTRIBUTES指针变量,指向一个描述对象属性的结构。OBJ_KERNEL_HANDLE,OBJ_OPENLINK,OBJ_EXCLUSIVE,OBJ_PERMANENT及OBJ_INHERIT都不是port对象的合法属性。

MaxConnectionInfoLength 输入 ULONG表示通过port发送的最大数据字节数。

MaxMessageLength 输入 ULONG表示通过port发送的最大消息字节数。

MaxPoolUsage 输入 ULONG表示用于消息存储的非分页池的最大数量。0表示使用默认值。

ZwCreatePort 将验证(MaxDataSize <= 0x104) 且(MaxMessageSize <= 0x148)。

NTSTATUS

NTAPI

NtConnectPort(

OUT PHANDLE PortHandle,

IN PUNICODE_STRING PortName,

IN PSECURITY_QUALITY_OF_SERVICE SecurityQos,

IN OUT PPORT_VIEW ClientView OPTIONAL,

OUT PREMOTE_PORT_VIEW ServerView OPTIONAL,

OUT PULONG MaxMessageLength OPTIONAL,

IN OUT PVOID ConnectionInformation OPTIONAL,

IN OUT PULONG ConnectionInformationLength OPTIONAL

);

功能描述:

创建一个连接命名port的port(客户端)

参数:

PortHandle 输出 PHANDLE 指针变量,指向客户端通信端口。

PortName 输入 PUNICODE_STRING指向一个结构体,该结构体描述了要连接的命名port

SecurityQos 输入 PSECURITY_QUALITY_OF_SERVICE Points to a structure that specifies the level of impersonation available to the port listener

ClientView 输入 输出 可选参数 PPORT_VIEW指针变量,指向一个结构体。该结构体描述了用于发送大量数据给listener的共享内存区,如果调用成功,该结构将被更新。

​ ServerView 输出 可选参数 PREMOTE_PORT_VIEW指针变量,指向一个调用者分配的缓冲区,该缓冲区接受由listener用于发送大量数据给调用者的共享内存区上的消息。

​ MaxMessageLength 输出 可选参数 PULONG表示能够通过port发送的数据的最大字节数

​ ConnectionInformation 输入 输出 可选参数 PVOID指向一个调用者分配的内缓冲区,该缓冲区表示要发送给listener的连接消息并,接收到来自listener的连接消息。

​ ConnectionInformationLength 输入 输出 可选参数 PULONG表示发送给listener的连接数据字节数,接收由listener发送的连接数据的字节数。

NTSTATUS

NTAPI

NtRequestWaitReplyPort(

IN HANDLE PortHandle,

IN PPORT_MESSAGE RequestMessage,

OUT PPORT_MESSAGE ReplyMessage

);

功能描述:

​ 给port发送一上请求数据并等待响应(客户端)

参数:

​ PortHandle 输入 HANDLE 一个port对象的句柄。该句柄不需要指定任何的访问规则

​ RequestMessage 输入 PPORT_MESSAGE 指向一个调用者分配的缓冲,该缓冲描述了发送给port的请求消息

​ ReplyMessage 输出 PPORT_MESSAGE 指向一个调用者分配的缓冲,该缓冲接发送给port的响应消息。

NTSTATUS

NTAPI

NtCreateSection(

OUT PHANDLE SectionHandle,

IN ACCESS_MASK DesiredAccess,

IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,

IN PLARGE_INTEGER MaximumSize OPTIONAL,

IN ULONG SectionPageProtection,

IN ULONG AllocationAttributes,

IN HANDLE FileHandle OPTIONAL

);

功能描述:

​ 创建一个section对象

参数:

​ SectionHandle 输出 PHANDLE 如果函数成功调用将会得到一个section对象的句柄。

​ DesiredAccess 输入 ACCESS_MASK 表示调用者调用者需要的section对象的访内类型。该参数可以为0,也可以是以下标志的组合:

​ SECTION_QUERY - 查询访问

​ SECTION_MAP_WRITE - 当被关联时(mapped)可被写入

​ SECTION_MAP_READ - 当被关联时(mapped)可被读取

​ SECTION_MAP_EXECUTE - 当被关联时(mapped)可被执行

​ SECTION_EXTEND_SIZE - 扩展访问

​ SECTION_ALL_ACCESS - 上面全部访问类型 + STANDARD_RIGHTS_REQUIRED

​ ObjectAttributes 输入 可选参数 POBJECT_ATTRIBUTES 指向一个结构体,该结构表示了对象的属性。OBJ_OPENLINK是不合法的属性对于section对象而言。

​ MaximunSize 输入 可选参数 PLARGE_INTEGER 表示section的字节数。如果 FileHandle为0,这个值必需指定,否则,它将默认为由FileHandle指示的文件的大小。

​ SectionPageProtection 输入 ULONG 表示页面保护的类型当section被关联时(mapped)。这个参数可以是以值中的任意一个:

​ PAGE_READONLY

​ PAGE_READWRITE

​ PAGE_WRITECOPY

​ PAGE_EXECUTE

​ PAGE_EXECUTE_READ

​ PAGE_EXECUTE_READWRITE

​ PAGE_EXECUTE_WRITECOPY

​ AllocationAttributes 输入 ULONG 表示了section的属性。该参数可以是下面这些值的组合:

​ SEC_BASED 0x00200000 // 在每个进程中关联(map)section到同一地址

​ SEC_NO_CHANGE 0x00400000 // 禁止页面保护的改变

​ SEC_IMAGE 0x01000000 // 关联(map)section为一个映象(image)

​ SEC_VLM 0x02000000 // 在VLM区内关联(map)section

​ SEC_RESERVE 0x04000000 // 保留不用分配页面文件存储 Reserve without allocating pagefile storage

​ SEC_COMMIT 0x08000000 // 提交页(Commit pages);默认的形为

​ SEC_NOCACHE 0x10000000 // 标记页为无缓冲区的 Mark pages as non-cacheable

​ FileHandle 输入 可选参数 HANDLE 指示用于创建section对象的文件。该文件被打开的访问方式要与由保护参数指定的保护类型标志相兼容。如果FileHandle为0,则函数在文件系统中创建一个由页面文件的而不是命名文件支持的指定大小的section对象。

IPC隔离

Ipc_Init:

进行hook的安卓,调用lpc_CreateObjects函数,函数内部会通过创建一个任意对象,获取兑现管理器命名空间,此后所有的对象句柄名位于这个命名空间下,如果不存在命名空间,则发送code,让驱动通过zwcreatesymboliclinkobject创建一个.

Ipc_GetName

如果RootDirectory不为空,并且请求的RootDirectory句柄类型为一个文件目录,允许.

如果既指定了objectName,又指定了路径,则返回一个特殊的状态.

通过NtQueryObject查询父目录句柄名称.

对创建句柄时的名称,进行了重命名处理

Ipc_NtCreateEvent

对于未命名的事件内核对象不作处理,sbie的理解应该是未命名的Event对象无法跨进程使用.所以不做隔离

对象完整信息的获取,传参RootDirectory和ObjectName调用Ipc_GetName.

创建如果没有指定对象管理器命名空间句柄,直接设置RootDirectory指定句柄.

如果指定了,但是是一个目录不作处理.

否则返回一个特殊的状态.

调用原函数

image-20221012171457582

image-20221012171518546

Ipc_CreateObjects:

首先会通过CreateEvent获取完整对象句柄名,然后获取对象管理器命名空间,如果获取不到,

向驱动通信,驱动通过函数zwcreatesymboliclinkobject创建一个对象管理器命名空间,此后该进程下创建的对象,都位于这个命名空间下.

NtAlpcQueryInformationMessage

某些情况下会返回失败,sbie做的处理只是将返回值修改.

Ipc_ConnectProxyPort

代理的lpc端口

\\RPC Control\\ntsvcs 服务管理器端口

\\RPC Control\\plugplay plug and play服务端口 这个服务是用来检测一些插入的硬件设备的

ClientSharedMemory,ServerSid,ServerSharedMemory必须为null,否则结束流程.

获取了请求长度,设置长度不大于1024字节.

转发至了sbieSvc服务,由sbiesvc代理连接一个ipc端口,\\RPC Control \\Sbiesvc Port”;调用原始函数,记录日志.最后对数据进行了兼容性处理,调整了偏移SbieSvc是一个64位进程.

没有做共享内存相关的处理.

NtAlpcSendWaitReceivePort hook

NtConnectPort hook

连接指定端口,它主要对连接的端口,修改至了当前沙箱的对象句柄命名空间下

NtAlpcConnectPortEx hook

对安全描述符进行了处理,为了成功连接至端口,会将安全描述符修改为null.

IPC_GetServerEvent

获取一个服务的事件标识,会根据这个标识,判断是否要启动一个sbie的crypt组件的服务,在构建证书连接上下文hook函数中调用

Ipc_StartServer

某些端口是一些服务注册的,连接前要先启动服务.

如果端口为actkernel,则启动sbie的DcomLaunch服务,如果是epmapper服务.则启动sbie的rpcss服务

RPC隔离

RpcRt_Init

hook安装

RpcStringBindingComposeW hook

原始函数作用

生成一个以字符串表示的绑定句柄(bindinghandle)并为此字符串分配内存。因为服务指定了使用本地命名管道作为网络通信介质,所以客户端在建立于服务器的连接时也必须指定相同的管道参数。

首先判断当前函数返回地址是否在sppc.dll模块空间下.

sppc.dll是微软软件保护相关的实现,首先将目标alpc端口修改为”SPPCTransportEndpoint-00001”,这个端口是sppsvc.exe注册的.

然后启动sppsvc服务.

而后调用原始api.

这个函数主要对软件保护相关进行了处理,指定了端口,并启动原始服务.

RpcBindingFromStringBindingW hook

原始函数作用

从字符串表示的绑定句柄,创建了一个服务绑定句柄RPCServer_IfHandle。绑定之后客户端便可以请求服务器的服务(调用服务器的函数)。

首先判断是否是一个活动端口.

判断调用者的内存空间是否在winspool.drv下,winspool.drv是打印管理相关服务.

由于是活动端口,当以上条件符合,会调用函数从r0枚举aplc端口,获取PrintSpooler的对应活动端口.

需要注意的是win8.1之前,该端口名是固定的\RPC Control\spoolss,之后的版本,由spoolsv.exe在系统范围内注册了一个动态的端口.端口映射器\RPC Control\epmapper,hook后绕过了这个端口映射器.

而后判断此dll是否开启了超时时间的设置,而后调用原始函数,设置超时时间.

RpcBindingCreateW hook

快速建立新的rpc连接

根据情况会指定目标端口,如果uuid==RPC interfaceId,并且指定的是本机的rpc.并且是动态端点.并且指定的uuid=906B0CE0-C70B-1067-B317-00DD010662DA

指定为:samss lpc,这里为什么指定这个端口呢.

samss lpc是lsass.exe的端口,本地安全服务

RpcBindingInqAuthClientEx hook

获取发出远程调用的客户端的扩展信息

这里主要对返回值做了处理,某些特殊情况下会获得失败的返回值,如宿主机安装了某些杀软

NdrClientCallX

没做什么特殊的处理,只是打印了一些日志,进行了一些调用的监控.

Ndr64AsyncClientCall

这个函数是异步的,主要在uac提权相关会用.

RpcAsyncCompleteCall

实现权限相关

=======================================================================