长亭百川云 - 文章详情

Windows Container逆向工程分析

kernsec

62

2024-07-13

1 简介

    windows容器基于两种机制实现,一个使用虚拟化技术将container放在hypvervirsor中执行,另一个是使用叫做server silo的技术利用内核提供的namespace隔离机制。本文只分析server silo的实现机制,基于win10专业版10.0.19045进行逆向工程分析。

2 Server silo实现原理

2.1 sdk定义

  微软公司未提供server silo的用户态接口信息,内核接口可以在ntddk.h中找到,这意味着微软不希望用户使用容器技术,只有微软公司自身才能使用。

  Server silo基于windows job来实现,首先建立一个job object,然后使用SetInformationJobObject函数启动silo,可以在winnt.h中看到以下定义:

typedef enum _JOBOBJECTINFOCLASS {

  黄色部分是silo要使用的值,但是虽然在头文件中找到了JobObjectSiloBasicInformation的定义,但是在笔者的win10专业版本的内核中并未提供此接口的实现,猜测可能在win10 server中有相关实现。

typedef struct _SILOOBJECT_BASIC_INFORMATION {

  这个接口的相关顺序如下:

1、首先使用35调用PspCreateSilo分配一个silo storage并设置silo标志。

2、使用37建立silo的root directory和相关namespace空间。

3、使用40进一步做silo的初始化。

4、使用41结束silo服务。

5、使用47控制silo服务状态。

2.2 相关内核数据结构

lkd> dt nt!_eprocess

_ESERVERSILO_GLOBALS结构保存silo的状态信息。

1: kd> dt nt!_ESERVERSILO_GLOBALS

2.3内核接口实现

2.3.1 PspCreateSilo

  建立silo的storage结构。

PAGE:00000001405D3FAC PspCreateSilo proc near               ; CODE XREF: NtSetInformationJobObject+1A1C↓p

  如果当前进程已经有一个silo,则直接返回。

PAGE:00000001405D3FE5 cmp     [rbx+518h], rdi

  判断ejob->Storage如果为空则调用PspAllocStorage分配一个silo context。

PAGE:00000001405D4013 call    PspJobHasChildren

  如果job存在子进程,设置esi为0C000050Fh。

PAGE:00000001405D4023 loc_1405D4023: ; CODE XREF: PspCreateSilo+6E↑j

  如果ejob->Silo == 1,设置esi为0C0000508h。

PAGE:00000001405D404A loc_1405D404A: ; CODE XREF: PspCreateSilo+95↑j

  将当前ejob->Storage值与PspAllocStorage分配的地址互换。

  接着看下PspAllocStorage的实现:

PAGE:000000014064EF40 ; __int64 __fastcall PspAllocStorage(_QWORD *)

  微软符号表中并没有给出_PSP_STORAGE结构体的定义。

lkd> dt nt!_PSP_STORAGE

  但是根据传递给ExAllocatePoolWithTag函数的第二个参数为240h,所以可以断定_PSP_STORAGE结构体大小为240h。

PAGE:000000014064EF67 lea     ecx, [r8+20h]

  ecx设置为32,将storage地址依次设为0,可以试着组建一下_PSP_STORAGE结构体:

struct _PSP_STORAGE {

2.3.2 PspSetJobSiloThreadImpersonationPolicy

  启用或关闭silo,它有两个参数第一个为ejob结构,第2个为silo的状态值。

AGE:0000000140909864 PspSetJobSiloThreadImpersonationPolicy proc near

 如果第二个参数不为2,强行设为2,如果ejob->EnableUsermodeSiloThreadImpersonation已经置位则返回。

PAGE:0000000140909884

  设置ejob->EnableUsermodeSiloThreadImpersonation |= 2。

2.3.3 ObInitServerSilo

PAGE:00000001407C0E70 ; NTSTATUS __fastcall ObInitServerSilo(__int64)

  初始化ejob->ObSiloState结构,ObSiloState->PrivateNamespaceLookupTable是一个哈希表,每个项是一个双向链表节点,blink,flink都指向自己。

PAGE:000000014085E753 call    PsGetPermanentSiloContext

    ejob->0x518地址保存silo context。

2.3.4 PspInitializeProtectedProcessParameters

  设置进程的enviorment内容

PAGE:00000001407BC430 ; __int64 __fastcall PspInitializeProtectedProcessParameters(__int64)

  首先设置_ESERVERSILO_GLOBALS->PsProtectedCurrentDirectory

PAGE:00000001407BC457 movzx   eax, word ptr [rcx+430h]

  构造PsProtectedEnvironment内容:

+-------------------------------------------------------------------------------------------------------------------+

| Path= | NtSystemRoot->Buffer| \\System32 | SystemDrive= | NtSystemRoot->Buffer[0]|SystemRoot=|NtSystemRoot->Buffer |

+-------------------------------------------------------------------------------------------------------------------+

  一共3个环境变量: Path、SystemDrive、SystemRoot

2.3.5 PspSiloInitializeSystemRootSymlink

PAGE:0000000140906AF0 ; NTSTATUS __fastcall PspSiloInitializeSystemRootSymlink(_LIST_ENTRY *)

  调用未公开接口ZwCreateSymbolicLinkObject建立\\SystemRoot到\\GLOBAL??\\ + _ESERVERSILO_GLOBALS->NtSystemRoot的软链接?

2.3.6  PspSiloInitializeUserSharedData

PAGE:0000000140906C1C ; __int64 __fastcall PspSiloInitializeUserSharedData(_LIST_ENTRY *)

  设置silo的serSharedData和UserSharedSection字段。

2.3.7 PspSiloInitializeLicenseData

PspSiloInitializeLicenseData函数初始化silo的License Data

.text:00000001405B03A8 ; __int64 __fastcall PspSiloInitializeLicenseData(_LIST_ENTRY *)

  分配struct _EXP_LICENSE_STATE结构体,微软符号表中未提供它的数据结构。

.text:00000001405B049F mov     [rbx+388h], rsi ; ejob->ExpLicenseState

    ExpInitLicensing 做简单初始化,如果是全局silo变量PspHostSiloGlobals,使用ExpHostBootLicensingData作为默认的license data。

.text:00000001405B04D7 lea     rdx, aProductoptions_0 ; "ProductOptions"

  查询注册表ProductOptions键值,ExpQueryRegistryRoutine为回调函数。

      ExInitLicenseData初始化license的关键函数,过于复杂,未跟踪。

2.4 Namespace隔离

2.4.1 注册表隔离

  每个silo进程都有独立的注册表namespace。

PAGE:000000014068B2E0 CmpParseKey proc near

   CmpGetRegistryNamespaceRootForSilo调用PsGetPermanentSiloContext获取ejob->Storage的地址。

PAGE:0000000140687B1F mov     rax, [rax+20h]

    ejob->Storage.slots[32]为vreg namespace的地址。

2.4.2 进程隔离

PAGE:00000001406746A0 ; NTSTATUS __stdcall PsLookupThreadByThreadId(HANDLE ThreadId, PETHREAD *Thread)

    PspReferenceCidTableEntry函数根据ThreadId返回对应的ethread结构体。

PAGE:00000001406746D7 call    PsGetCurrentServerSilo

  得到当前进程的ejob结构体。

PAGE:000000014067470D loc_14067470D: ; CODE XREF: PsLookupThreadByThreadId+48↑j

    PsIsProcessInSilo函数有两个参数,第一个参数为要得到handle的目标进程kprocess结构体, 第二个参数为当前进程的ejob结构体,如果返回值为0就不能获取目标进程的Handle,为1则可以。

下面仔细分析下这个函数:

.text:0000000140273948 PsIsProcessInSilo proc near ; CODE XREF: PsIsThreadInSilo+29↑p

  如果第二个参数ejob为空则返回1。

.text:0000000140273959

  如果第一个参数kprocess为PsInitialSystemProcess或PsIdleProcess同样返回为1。

.text:000000014027396B mov     rcx, [rcx+510h] ; eprocess->job

    PspGetJobSilo函数从当前进程的ejob开始向父进程ejob查找到第一个包含silo状态的ejob结构体。

.text:0000000140273977 mov     rcx, rax

    PspIsSiloInSilo函数包含两个参数,第一个参数为目标进程包含silo状态的ejob结构体,第二个参数为当前进程的ejob结构体。如果返回值不为0,则返回1。

PAGE:000000014064BB6C PspIsSiloInSilo proc near ; CODE XREF: PsIsProcessInSilo+32↑p

  如果第二个参数ejob为0,则返回1。

PAGE:000000014064BB75 loc_14064BB75: ; CODE XREF: PspIsSiloInSilo+3↑j

  通过一个循环遍历第一个参数及它的父进程ejob是否和第二个参数相等,如果相等返回1,否则返回0。所以通过上述两个函数的分析,一个进程能否获取另一个进程的handle,有以下几种情况:

1、如果当前进程没有silo结构,也就是说当前进程在容器外,是可以得到容器内进程的handle。

2、PsInitialSystemProcess和PsIdleProcess进程能获取任何容器内进程的handle。

3、容器内的进程是不能获取容器外的任何进程handle。

4、只有父亲容器可以获取子容器内进程的handle,反之不可以。

2.4.3 驱动隔离

    silo进程内不能安装和卸载驱动程序。

.text:0000000140399388 ; __int64 __fastcall IopLoadDriverImage(_OWORD *)

  如果进程在container内是不允许加载驱动程序的。

PAGE:0000000140762F24 IopUnloadDriver proc near ; CODE XREF: PnpUnloadAttachedDriver+A1↑p

  同理,contianer进程不能卸载驱动程序。

2.4.4 文件隔离

AGE:000000014068F290 ObpLookupObjectName proc near ; CODE XREF: ObReferenceObjectByNameEx+156↑p

  如果在容器中则通过PsGetPermanentSiloContext获取root directory, 否则使用ObpRootDirectoryObject作为root directory。

.text:00000001402772A0 PsGetPermanentSiloContext proc near ; CODE XREF: CmGetRootKeyObjectForSilo+17↓p

    ejob->Storage[PsObjectDirectorySiloContextSlot].value为当前容器的root directory。

   OBP_GET_SILO_ROOT_DIRECTORY_FROM_SILO函数有同样的功能。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2