2.windows类型开发
cve-2021-36955 CLFS提权
cve-2022-24521CLFS提权
cve-2022-37969有exp
3.linux内核uaf
cve-2017-11176实现getshell 命令执行
NtCreateFile Windbg BINDIFF
内核下的一般漏洞分析,通常使用Windbg远程双机调试的方式,对于特殊漏洞,比如虚拟化漏洞相关,可以采用IDA + VMWare GdbStub的方式。
漏洞分析三要素:
调试工具的熟练程度:Windbg
漏洞相关的知识与经验:asm、漏洞机理和防护机制等
对于特定程序的理解程度:eg. 内核下的进程管理、对象管理、安全管理等。
(CLFS) 日志文件的基本数据结构。用户可通过CreateLogFile函数来创建日志文件,函数执行后会在本地创建一个同名的后缀为.blf的日志文件,CLFS会将文件的数据加载进内存中进行解析处理。每个日志块以日志块头部_CLFS_LOG_BLOCK_HEADER开始,,其定义为:
typedef struct _CLFS_LOG_BLOCK_HEADER
{
UCHAR MajorVersion;
UCHAR MinorVersion;
UCHAR Usn;
CLFS_CLIENT_ID ClientId;
USHORT TotalSectorCount;
USHORT ValidSectorCount;
ULONG Padding;
ULONG Checksum;
ULONG Flags;
CLFS_LSN CurrentLsn;
CLFS_LSN NextLsn;
ULONG RecordOffsets[16];
ULONG SignaturesOffset;
} CLFS_LOG_BLOCK_HEADER, *PCLFS_LOG_BLOCK_HEADER;
重点关注下Checksum、RecordOffsets和SignaturesOffset,Checksum是该日志块数据的校验和,在读取数据时会对该数据进行校验,采用的是CRC32的校验方式,对应函数为CCrc32::ComputeCrc32:
__int64 __fastcall CCrc32::ComputeCrc32(const unsigned __int8 *const data, int size)
{
v2 = 0i64;
v3 = size;
for ( i = -1; v3; --v3 )
{
v5 = data[v2];
v2 = (v2 + 1);
i = (i >> 8) ^ CCrc32::m_rgCrcTable[i ^ v5];
}
return ~i;
}
RecordOffsets保存每一个记录的偏移值,第一个记录与BlockHeader相连,偏移为sizeof(CLFS_LOG_BLOCK_HEADER),也就是0x70;SignaturesOffset字段保存了一块内存的偏移值,日志在编码时每0x200字节的最后两个字节将被签名所覆盖,被覆盖前的数据将存放在SignaturesOffset字段所计算偏移的内存中:
do
{
DataStore += 2i64;
v16 = 0x20;
v17 = 0x40;
if ( *(record + 4) - 1 != v15 )
v16 = 0;
if ( v15 )
v17 = 0;
v18 = v17 | v16;
v19 = v15 << 9;
LOBYTE(Signatures) = a4 | v18;
++v15;
*(DataStore - 2) = *(v19 + record + 0x1FE);// store previous data
*(v19 + record + 0x1FE) = Signatures; // write Signatures
}
while ( v15 < v10 );
v12 = *(record + 0x10);
当解码时再将这段内存中保存的数据写回到原来的区域,编码和解码对应的函数分别为ClfsEncodeBlock和ClfsDecodeBlock。
接下来是日志文件的元数据块,在日志文件中有6个不同的元数据块,分别为Control Record、Base Record、Truncate Record以及三个对应的shadow blocks。在每个元数据块开始也会有一个_CLFS_LOG_BLOCK_HEADER用来保存一些基本信息。Control Record的定义为:
typedef struct _CLFS_CONTROL_RECORD
{
CLFS_METADATA_RECORD_HEADER hdrControlRecord;
ULONGLONG ullMagicValue;
UCHAR Version;
CLFS_EXTEND_STATE eExtendState;
USHORT iExtendBlock;
USHORT iFlushBlock;
ULONG cNewBlockSectors;
ULONG cExtendStartSectors;
ULONG cExtendSectors;
CLFS_TRUNCATE_CONTEXT cxTruncate;
USHORT cBlocks;
ULONG cReserved;
CLFS_METADATA_BLOCK rgBlocks[ANYSIZE_ARRAY];
} CLFS_CONTROL_RECORD, *PCLFS_CONTROL_RECORD;
其中_CLFS_METADATA_BLOCK定义为:
typedef struct _CLFS_METADATA_BLOCK
{
union
{
PUCHAR pbImage;
ULONGLONG ullAlignment;
};
ULONG cbImage;
ULONG cbOffset;
CLFS_METADATA_BLOCK_TYPE eBlockType;
} CLFS_METADATA_BLOCK, *PCLFS_METADATA_BLOCK;
这里保存了元数据块的大小、偏移、类型等重要数据。
Base Record定义为:
typedef struct _CLFS_BASE_RECORD_HEADER
{
CLFS_METADATA_RECORD_HEADER hdrBaseRecord;
CLFS_LOG_ID cidLog;
ULONGLONG rgClientSymTbl[CLIENT_SYMTBL_SIZE];
ULONGLONG rgContainerSymTbl[CONTAINER_SYMTBL_SIZE];
ULONGLONG rgSecuritySymTbl[SHARED_SECURITY_SYMTBL_SIZE];
ULONG cNextContainer;
CLFS_CLIENT_ID cNextClient;
ULONG cFreeContainers;
ULONG cActiveContainers;
ULONG cbFreeContainers;
ULONG cbBusyContainers;
ULONG rgClients[MAX_CLIENTS_DEFAULT];
ULONG rgContainers[MAX_CONTAINERS_DEFAULT];
ULONG cbSymbolZone;
ULONG cbSector;
USHORT bUnused;
CLFS_LOG_STATE eLogState;
UCHAR cUsn;
UCHAR cClients;
} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;
其中cActiveContainers保存了当前活跃的容器数,rgContainers数组则保存容器上下文的偏移值。用户可以使用AddLogContainer函数向日志中添加容器,容器对应的上下文结构为:
typedef struct _CLFS_CONTAINER_CONTEXT
{
CLFS_NODE_ID cidNode;
ULONGLONG cbContainer;
CLFS_CONTAINER_ID cidContainer;
CLFS_CONTAINER_ID cidQueue;
union
{
CClfsContainer* pContainer;
ULONGLONG ullAlignment;
};
CLFS_USN usnCurrent;
CLFS_CONTAINER_STATE eState;
ULONG cbPrevOffset;
ULONG cbNextOffset;
} CLFS_CONTAINER_CONTEXT, *PCLFS_CONTAINER_CONTEXT;
pContainer实际上包含一个内核指针,指向CClfsContainer在运行时描述容器的类。当日志文件在磁盘上时,该字段必须设置为零。
重点关注CClfsContainer字段,该字段在内存中指向CClfsContainer类的对象指针,因为这是一个内核指针,所以不会返回给磁盘的日志文件中,以避免信息泄露。当在内存加载时,CClfsBaseFilePersisted::LoadContainerQ函数中会调用容器类构造函数CClfsContainer::CClfsContainer将返回的对象指针赋值给该字段。
1.cve-2021-36955 Windows CLFS驱动提权
可利用度为more likely(很可能被利用),同时利用代码成熟度为Functional(可编写出利用代码)
0x01 简介:公用日志文件系统 (CLFS) 是一种通用日志记录服务,可在用户模式或内核模式下运行的软件客户端使用,用以构建一个高性能的事务日志。可供在用户模式或内核模式下运行的软件应用程序用于记录数据和事件并优化日志访问。自 Windows Vista 和 Windows Server 2003 R2中被引入用于构建高性能事务日志以来,使用者可以通过CLFS提供的API对日志进行创建、存储、读取等操作。
漏洞概述:CLFS公共日志文件系统漏洞。针对微软内核模块 clfs.sys。clfs.sys 驱动对 Base Log Record 的数据处理存在一处缺陷,导致攻击者可以通过精心构造的BaseLog Record 来传入一个伪造的 CclfsContainer 对象。由于被伪造的 CclfsContainer 对象数据完全可控,攻击者通过伪造CclfsContainer对象的虚函数表和其他数据,劫持了CClfsBaseFilePersisted::RemoveContainer函数内调用的两个虚函数CClfsContainer::Release 和 CClfsContainer::Remove,并结合其他伪造的数据实现了一个任意地址写入原语,可以将任意数据写到任意内核地址。
漏洞描述:(分析步骤)
使用 BINDIFF 对比更新前后变化。补丁后的 clfs 模块中增加了一个名为 CClfsBaseFile::IsValidOffset(ulong) 的函数。该函数在 CClfsBaseFile::GetSymbol(long,ulong,_CLFS_CONTAINER_CONTEXT * *) 函数流程中用于对数据合法性的校验。
通过对更新前后的clfs.sys 文件进行反汇编,并定位到 CClfsBaseFile::GetSymbol(long,uchar,_CLFS_CLIENT_CONTEXT * *) 函数的位置进行分析,跟进 clfs 模块并进入到该函数中,我们发现在更新前的版本中,会直接对传入的第二个参数——BaseLogRecord 对象中的偏移值计算为 _CLFS_CONTAINER_CONTEXT 对象地址并使用,并不会判断该对象的合法性,也即对象内数据是否合法,是否已经损坏。
而在更新后的版本中,在进行地址计算前,通过调用新增的 CClfsBaseFile::IsValidOffset(ulong) 对传入的参数二BaseLogRecord 对象中的偏移地址所求得的 RecordIndex 进行判断,以验证 BaseLogRecord 对象的合法性。
漏洞修补后,在CClfsBaseFile::GetSymbol(long,uchar,_CLFS_CLIENT_CONTEXT * *) 函数的入口处,通过调用 CClfsBaseFile::IsValidOffset(ulong) 对传入的参数二 BaseLogRecord 对象中的偏移地址所求得的 RecordIndex 进行判断,用以验证对应的 BaseLohRecord 对象的合法性。
接着转到 CClfsBaseFile::GetSymbol(long,uchar,_CLFS_CLIENT_CONTEXT * *) 函数的上层调用 CClfsBaseFilePersisted::RemoveContainer ,在该函数中,通过调用 GetSymbol 获取容器上下文 _CLFS_CONTAINER_CONTEXT 对象。
由于对于传入的对象未进行完整的有效性检测,导致会将一个包含错误 Container 对象的容器上下文经 GetSymbol 返回,在取得错误的Container 对象后,如果对该对象进行解析,则会造成漏洞。
如果在用户层可以实现对 Container 对象的控制,就可以通过伪造该对象,从而进一步修改这两处的回调函数地址,实现内核的任意函数执行。
事实上,通过分析发现,在用户层通过构造 BaseLogRecord 对象可以实现将伪造的Container向内核传递,并通过释放对应的句柄实现进入 CClfsBaseFilePersisted::RemoveContainer 函数内进行利用。通过分析样本程序,我们得到了上述利用过程。接着,通过对漏洞利用程序中的伪造Container 进行修改,实现对漏洞利用的回调函数进行干预,触发蓝屏。内核由于调用非法内存空间造成蓝屏。
影响版本:
0x04 环境搭建
0x05 漏洞复现
一. 判断当前操作系统版本等信息
调用 GetLogicalProcessorInformation 获取CPU信息并设置内核池对齐参数(一般是40H)同时判断是否为X64平台(该样本工具只针对X64平台),同时调用 RtlGetVersion 获取 windows 版本号,其中win10系统判断了从win10 1507到 20h1的所有小版本。
然后根据内核版本,设置利用需要的内核偏移,比如gs:[188h]的位置存储的是_KTHREAD结构的地址,这个结构基址在WIN10下+0x220的位置存储的就是_KPROCESS,通过该结构可以获取 SYSTEM 进程的 TOKEN ,然后复制给新创建的CMD进程。
同时也会根据不同系统版本在 hal 或者 ntoskrnl 中利用硬编码去定位函数地址。在win7下会获取 XmXchgOP 和 HalpDmaPowerCriticalTransitionCallback 的地址,在后续操作中通过在恶意blf文件中设置这两个地址,漏洞触发就后会调用这两个函数。
另外还会调用 NtQuerySystemInformation 函数,使用 SystemModuleInformation 类型用来获取 ntoskrnl 模块和 hal 模块的地址。
最终会构造包含如下成员的自定义结构:
struct VulATTRIBUTE
{
HANDLE hReadPipe; //管道读句柄
HANDLE hWritePipe; //管道写句柄
DWORD Tag1; //管道属性名
ULONG64 pooladdr1; //管道属性内核地址
DWORD Tag2; //管道属性名
ULONG64 pooladdr2; //管道属性内核地址
PVOID pReadAttribute;
ULONG64 HALaddr; //HAL基地址
ULONG64 NTOSKRNLaddr; //ntoskrnl基地址
}
二.创建恶意 blf 记录构造任意地址写
可以通过应用层CLFS API的操作去生成恶意blf文件,首先会访问 \\GLOBAL\\LOG 对象判断日志文件系统是否正常工作,后续对 blf 文件的操作与普通文件是一样的,但会触发 clfs.sys 的解析。
最后调用 SetFileInformationByHandle 删除文件后在驱动层会触发对恶意clfsContainer的解析,进而触发内核任意内容写任意地址。
具体流程为:
// 1. 在TEMP目录创建 wct打头的临时blf文件 ,判断日志文件系统是否正常工作。
// 2. 创建恶意 blf文件内容。
先生成blf文件,并将800字节的 control record 数据写入其中。
// 2.1生成tmp文件,并写入512KB大小的数据,其中包括仅有_CLFS_LOG_BLOCK_HEADER 结构的损坏数据到其中用于对漏洞点的攻击。在前述blf文件中生成 baselog recoder :创建 BaseLogRecord 结构,并将 tmp 文件的路径和可以在用户层控制的地址块写入其中。
// 2.2 使用 NtCreateFile 打开 blf 文件句柄,这时 clfs.sys 将对该文件进行解析,并以此 blf 为 clfs 的 RootDirectory ,打开名为“test”对应由blf 知道的目录相对路径名,实际上就是 tmp 文件的句柄。
通过Windbg 查看文件句柄,可以查看到这两文件的句柄。
打开test 名称的文件句柄,可以看到该文件为wct4EFA.tmp文件的路径,且文件对象的DeleteAccess值为0。
// 2.3 最后调用 SetFileInformationByHandle 函数对刚刚创建的 test 文件设置可删除的文件属性并执行删除操作。在成功删除该文件后再次删除 blf 文件时,由于在 clfs 中对其对象未进行有效性检查造成漏洞。
最终在调用 SetFileInformationByHandle 触发漏洞后,由于伪造的CclfsContainer 对象没有完全的校验,导致用户可以对这个对象内的数据进行控制,在进入到内核层后,该对象在进入到 CClfsBaseFilePersisted::RemoveContainer 该函数时,由于用户层可以完全控制该对象的内部数据,导致在内核层使用该回调时可以进入到攻击者指定的内核函数中,比如在第一步通过硬编码获取的 XmXchgOP ,同时函数参数也可以在恶意文件中指定,最终就会造成任意内存地址写。
整个利用过程中,触发了两次漏洞,第一次是修改内核中pipe的attribute结构来构造内核读写原语,第二次是替换cmd进程的token。
三.内核读写原语与Token复制
可以使用了NPFS(命名管道文件系统)与 bigpool 的特性来获取 pipe attribute 结构的内核地址,这也算是一种绕过 KASLR 的技巧,与CVE-2021-31955类似。
具体过程为:
// 1.调用 NtCreateNamedPipeFile 创建一个名为"WWWAITER"的命名管道文件,然后调用 NtFsControlFile 控制码为0x110018。0x110018是FSCTL_PIPE_WAIT,暂停这个管道服务器。
// 2.调用 CreatePipe 创建一个匿名管道,并调用 NtFsControlFile 控制码为0x11003C,设置这个管道的attribute,样本中设置了两个pipe attribute。
设置了属性后,在内核中就会分配如下结构体来存放属性。
//PipeAttribute是未公开的结构体
struct PipeAttribute {
LIST_ENTRY list;
char * AttributeName;
uint64_t AttributeValueSize ;
char * AttributeValue ;
char data [0];
};
其中属性名 AttributeName 和属性值 AttributeValue 是指向数据区 data 不同偏移的两个指针,如果被修改就可以任意地址读。
// 3.接着调用 NtQuerySystemInformation 查询 SystemBigPoolInformation (0x42),PoolFlag == tApN的信息,这样就可以获取到内核中刚才创建的两个pipe attribute结构的地址。
注意:如果是调用 WriteFile 向管道 write_pipe 句柄写入数据,那么 tag 成员字符串是 rFpN ,调用 NtFsControlFile 设置管道属性的tag成员字符串是tApN ,不同 pool 的 tag 字符串可以参见 wdk 的 pooltag.txt 。
// 4.通过 clfs 漏洞将上一步获取到的内核地址作为目的地址,然后写入想要获取数据的地址,比如 SYSTEM 进程 TOKEN 的地址,再通过下一步就创造出了任意内核地址读原语。
// 5. 然后用 0x110038 控制码调用 NtFsControlFile 来读取pipe属性值,属性值指针和属性值大小在内核中将被用于读取属性值并返回给用户。因为属性值指针在上一步已经通过漏洞被修改,那么就可以在内核中任意读取数据了,但不能任意写。
将TOKEN写到自身进程EPROCESS
四.完成提权创建子进程
测试方法:
判断当前操作系统版本
➡创建PipeAttribute属性
➡解释任意地址读取方式
➡构造出任意地址读取原语
➡获取当前进程的Token地址
➡获得当前进程EPROCESS指针
➡完成提权创建子进程
➡完成整个漏洞利用过程
风险分析:
该漏洞不仅可以在本地利用,在 webshell 中也可以利用,致使该漏洞的实际威胁程度上升。建议用户重视该漏洞的威胁性,即时安装官方提供的补丁。
在win8以后的系统中,低信任级别的程序不再有权限调用NtQuerySystemInformation查询SystemBigPoolInformation和SystemModuleInformation,因此该样本的利用方式在沙盒程序中是无效的。
风险等级:7.8
修复方案:
升级补丁以修复漏洞
2.CVE-2022-24521 Windows CLFS本地提权漏洞 Windows 通用日志文件系统 (CLFS) 逻辑错误漏洞
时间线:2022-4-12 Microsoft发布、2022-4-15 NVD发布
发现厂商:NSA, CrowdStrike
0x01 简介:
漏洞概述:CVE-2022-24521是Windows中 CLFS的本地提权漏洞,影响Windows的CLFS(Common Log FileSystem)驱动程序。由于CLFS驱动程序中存在的缺陷导致攻击者可以提升其进程的特权级别。CLFS是Microsoft在Windows Vista和Windows Server 2003 R2中为实现高性能而引入的日志框架,它为专用客户端应用程序提供API函数来创建、存储和读取日志数据。并且多个客户端可以共享以优化日志访问。CLFS驱动本质上的作用就是对BLF 日志进行格式解析及处理。
通过精心构造的日志文件,可以触发任意代码执行,进行权限提升。该漏洞源于Windows系统在处理CLFS(公共日志文件系统)驱动程序中的输入时存在漏洞,攻击者可以通过向系统发送特制的请求来触发此漏洞,从而获得管理员权限。该漏洞源于程序执行流运行到漏洞代码处时缺少对数据必要的检查,导致内存空间被破坏。
漏洞类型:本地权限提升
漏洞描述:(分析步骤)
CVE-2022-24521漏洞点位于CLFS.sys的CClfsBaseFilePersisted::LoadContainerQ函数中,该函数第215行判断CLFS_CONTAINER_CONTEXT结构的cidQueue字段值是否为-1,当容器队列cidQueue值为-1时,会调用CClfsBaseFilePersisted::RemoveContainer函数,而由于CClfsBaseFilePersisted::RemoveContainer内部会引用pContainer字段,为防止用户在磁盘上破坏此数据,系统有意在调用前将pContainer字段置为0:
v41 = v38->cidQueue;
if ( v41 == -1 )
{
v38->pContainer = 0i64;
v20 = CClfsBaseFilePersisted::RemoveContainer(this, v30);
...
}
在CClfsBaseFilePersisted::RemoveContainer函数的第56行调用经过取值与校验后将调用CClfsBaseFilePersisted::FlushImage函数处理文件数据,,当结果大于等于0时,取_CLFS_CONTAINER_CONTEXT的pCaontainer字段值,并调用相关函数。(而后将调用pContainer对象中存储的函数):
v11 = CClfsBaseFilePersisted::FlushImage(this);
v9 = v11;
v16 = v11;
if ( v11 >= 0 )
{
pContainer = containerContext->pContainer;
if ( pContainer )
{
containerContext->pContainer = 0i64;
ExReleaseResourceForThreadLite(*(this + 4), KeGetCurrentThread());
v4 = 0;
(*(*pContainer + 0x18i64))(pContainer);
(*(*pContainer + 8i64))(pContainer);
v9 = v16;
goto LABEL_20;
}
goto LABEL_19;
}
查看pContainer对象的构造函数CClfsContainer::CClfsContainer,可以看到*pContainer处存放其虚函数表:
*this = &CClfsContainer::`vftable';
查看虚函数表可以看到+0x18,+0x8偏移处分别为CClfsContainer::Remove与CClfsContainer::Release函数。继续跟进CClfsBaseFilePersisted::FlushImage函数,其函数第8行内部将执行调用CClfsBaseFilePersisted::WriteMetadataBlock函数,在该函数中,首先会遍历每一个容器上下文,将pContainer先保存后置为0:
for ( i = 0; i < 0x400; ++i )
{
v20 = CClfsBaseFile::AcquireContainerContext(this, i, &containerContext);
v15 = this + 8 * i;
if ( v20 >= 0 )
{
*(v15 + 0x38) = containerContext->pContainer;
containerContext->pContainer = 0i64;
CClfsBaseFile::ReleaseContainerContext(this, &containerContext);
}
else
{
*(v15 + 0x38) = 0i64;
}
}
然后调用ClfsEncodeBlock函数,对数据进行编码,此时记录中每0x200字节的后两个字节将被写入到SignaturesOffset指向的内存中,接着调用CClfsContainer::WriteSector函数,然后调用ClfsDecodeBlock函数对数据进行解码,并将之前保存的pContainer值重新写回:
ClfsEncodeBlock(RecoderHeader, *(RecoderHeader + 4) << 9, *(RecoderHeader + 2), 0x10u, 1u);
v10 = CClfsContainer::WriteSector(
*(this + 0x13),
*(this + 0x14),
0i64,
*(*(this + 6) + 24 * v8),
*(RecoderHeader + 4),
&v23);
if ( v7 )
{
ClfsDecodeBlock(RecoderHeader, *(RecoderHeader + 4), *(RecoderHeader + 2), 0x10u, &v21);
v17 = (this + 0x1C0);
do
{
if ( *v17 && CClfsBaseFile::AcquireContainerContext(this, v6, &containerContext) >= 0 )
{
containerContext->pContainer = *v17;
CClfsBaseFile::ReleaseContainerContext(this, &containerContext);
}
++v6;
++v17;
}
while ( v6 < 0x400 );
}
以上流程看似不存在安全问题,但实际上CClfsBaseFile::AcquireContainerContext函数并不能执行成功,因为在执行CClfsBaseFilePersisted::FlushImage前会将rgcontainer数组中该容器偏移值置0:
CLFS!CClfsBaseFilePersisted::RemoveContainer+0xb9:
fffff807`807b6ef9 4283a4a72803000000 and dword ptr [rdi+r12*4+328h],0 ds:002b:ffffde8e`b6d12398=00001528
CLFS!CClfsBaseFile::AcquireContainerContext+0x7f:
fffff807`807c245f 8b94b928030000 mov edx,dword ptr [rcx+rdi*4+328h] ds:002b:ffffde8e`b6d12398=00000000
所以该pContainer指针并不能得到保护,而在调用ClfsEncodeBlock函数编码阶段,由于没有对_CLFS_LOG_BLOCK_HEADER中的SignaturesOffset字段进行合法校验,用户可以提前修改SignaturesOffset使其与_CLFS_CONTAINER_CONTEXT中pContainer指针相交,导致该字段指向的内存在编码时被写回覆盖pContainer指针,当FlushImage函数执行完毕,将调用pContainer的虚函数,攻击者通过对其进行伪造,可以达到任意函数调用的目的。对于漏洞利用,可以查看在虚函数调用过程前后的汇编代码:
mov rax, [rdi]
mov rax, [rax+18h]
mov rcx, rdi
call cs:__guard_dispatch_icall_fptr
mov rax, [rdi]
mov rax, [rax+8]
mov rcx, rdi
call cs:__guard_dispatch_icall_fptr
rdi的值为pContainer可以由攻击者通过漏洞进行伪造控制,由于CLFS驱动未开启SMAP,系统允许对用户内存空间进行访问,同时可以看到第一个参数rcx为rdi,所以只需要一些耐心寻找到合理的gadget,便可以实现权限提升:
(SMAP/SMEP:全称为Supervisor Mode Access Prevention(管理模式访问保护),其作用大致可以理解为,禁止内核对用户空间的访问,实现一个完全的用户空间权限控制,禁止以内核调用导致的超越权限操作,并不会以内核权限为最大而执行用户空间内的权限。)
(SMAP(Supervisor Mode Access Prevention,管理模式访问保护)和SMEP(Supervisor Mode Execution Prevention,管理模式执行保护)的作用分别是禁止内核访问用户空间的数据和禁止内核执行用户空间的代码。)
影响版本:
未安装相关补丁的:Win7至Win11
Windows Server2008至2022
【包括以下:Windows 10 for 32-bit/ x64-bit Systems
Windows 10 Version 1607for 32-bit/64-bit Systems
Windows 10 Version 1809/1909/20H2/21H1//21H2 for 32/64-bit/ARM64-based Systems
Windows 11 for ARM64-based/x64-based Systems
Windows 7 for 32-bit/x64-based Systems Service Pack 1Windows 8.1 for 32-bit/x64-based systems
Windows RT 8.1
Windows Server 2008 for 32-bit/x64-based Systems Service Pack 2Windows Server 2008 for 32-bit/x64-based Systems Service Pack 2 (Server Core installation)
Windows Server 2008 R2 for x64-based Systems Service Pack 1】
0x04 环境搭建
漏洞复现:
运行漏洞利用程序后,当前用户变更为system
要触发该漏洞,攻击者应仔细构建基本日志文件和相关容器,以绕过驱动程序代码中的不同检查。
PoC如下:
__int64 __fastcall CClfsBaseFile::GetSymbol(PERESOURCE *this, unsigned int a2, char a3, struct _CLFS_CLIENT_CONTEXT **a4)
{
...
if ( CClfsBaseFile::IsValidOffset((CClfsBaseFile *)this, a2 + 135) )
{
v11 = CClfsBaseFile::OffsetToAddr((CClfsBaseFile *)this);
if ( v11 )
{
if ( *(v11 - 3) != a2 )
{
v8 = -1073741816;
goto LABEL_5;
}
v12 = ClfsQuadAlign(0x88u);
// v13 is a pointer to ClientContext
if ( *(_DWORD *)(v13 - 0x10) == (unsigned __int64)(v14 + v12) && *(_BYTE *)(v13 + 8) == a3 )
{
*a4 = (struct _CLFS_CLIENT_CONTEXT *)v13;
goto LABEL_12;
}
}
}
...
LABEL_12:
if ( v10 )
{
ExReleaseResourceForThreadLite(this[4], (ERESOURCE_THREAD)KeGetCurrentThread());
return v15;
}
return v8;
}
实际调用方式:
mov rax, [rdi] ; pContainerVftbl
mov rax, [rax+18h] ; method_1
mov rcx, rdi ; save pointer to pContainer
; pass it as an argument
; for the controllable call
call cs:__guard_dispatch_icall_fptr
mov rax, [rdi]
mov rax, [rax+8] ; method_2
mov rcx, rdi
call cs:__guard_dispatch_icall_fptr
可控的地址pContainer作为参数传递给间接调用,因此我们可以使用任何RCX用作指针的小工具来执行任意读/写操作。
1.创建管道对象,使用NtFsControlFileAPI 添加管道属性:
...
CreatePipe( hR , hW , NULL , bufsize ) ;
...
NTSTATUS status = NtFsControlFile(
hR,
0,
NULL,
NULL,
&ret,
0x11003C,
input,
input_size,
output,
output_size
);
属性是键值对并存储在链表中。该PipeAttribute对象在分页池中分配,并在内核中由以下结构定义:
struct PipeAttribute {
LIST_ENTRY list ;
char * AttributeName;
uint64_t AttributeValueSize;
char * AttributeValue;
char data [0];
};
2.请注意,分配必须足够大(x86 上 4080+ 字节,x64 上 4064+ 字节)才能在大池 [ 5 ] 中处理。
3.每当内核模式组件分配超过上述限制时,就会进行大池分配。APINtQuerySystemInformation有一个专门为转储大池分配而设计的信息类。不仅包括它们的大小、标记和类型(分页或非分页),还包括它们的内核虚拟地址:
...
NTSTATUS status = STATUS_SUCCESS;
if (NT_SUCCESS(status = ZwQuerySystemInformation(SystemBigPoolInformation, mem, len, &len))) {
PSYSTEM_BIGPOOL_INFORMATION pBuf = (PSYSTEM_BIGPOOL_INFORMATION)(mem);
for (ULONG i = 0; i < pBuf->Count; i++) {
__try {
if (pBuf->AllocatedInfo[i].TagUlong == PIPE_ATTR_TAG) {
// save me
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DPRINT_LOG("(%s) Access Violation was raised.", __FUNCTION__);
}
}
}
...
使用这个特性,我们可以很容易地获取新创建的管道对象的地址。
4.分配 fake_pipe_attribute 对象以供稍后将其地址注入原始双向链表。我们将保存内核 pipe_attribute 指针如下:
...
fake_pipe_attribute = (PipeAttributes*)VirtualAlloc(NULL, ATTRIBUTE_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
...
fake_pipe_attribute->list.Flink = pipe_attribute_1;
fake_pipe_attribute->list.Blink = pipe_attribute_2;
fake_pipe_attribute->id = ANY;
fake_pipe_attribute->length = NEEDED;
...
5.使用以下命令获取选定的小工具模块基地址NtQuerySystemInformation:
ntStatus = NtQuerySystemInformation(SystemModuleInformation,
&module, /*pSysModInfo*/
sizeof(module), /*sizeof(pSysModInfo) or 0*/
&dwNeededSize );
{
...
if (STATUS_INFO_LENGTH_MISMATCH == ntStatus)
{
pSysModInfo = ExAllocatePoolWithTag(NonPagedPool, dwNeededSize, 'GETK');
if (pSysModInfo) {
ntStatus = NtQuerySystemInformation(SystemModuleInformation,
pSysModInfo,
dwNeededSize,
NULL );
if (NT_SUCCESS(ntStatus))
{
for (int i=0; i<(int)pSysModInfo->dwNumberOfModules; ++i)
{
StrUpr(pSysModInfo->smi[i].ImageName); // Convert characters to uppercase
if (strstr(pSysModInfo->smi[i].ImageName, MODULE_NAME)) {
pModuleBase = pSysModInfo->smi[i].Base;
break;
}
}
}
else { return; }
ExFreePool(pSysModInfo)
pSysModInfo = NULL;
}
}
...
}
- 触发 CLFS 错误,它允许我们调用模块小工具执行任意数据修改。正确完成后,我们将能够覆盖pipe_attribute_1->list.Flink并pipe_attribute_2->list.Blink使用fake_pipe_attribute指针。现在,通过请求读取 上的属性(NtFsControlFile使用 x110038 IOCTL 调用)pipe_attribute_1 / pipe_attribute_2,内核将使用用户空间中的PipeAttributethat 并因此完全受控:
AttributeValue对指针的控制和AttributeValueSize提供可用于获取EPROCESS地址的任意读取原语。
7.触发 CLFS 错误以覆盖用户模式进程令牌以提升到系统权限。
测试方法:
1、构造日志文件,使_CLFS_CONTAINER_CONTEXT结构的cidQueue字段值为-1,pCaontainer字段值为代码地址,签名缓冲区偏移指向pCaontainer所在地址。
2、调用CreateLogFile API打开已有的日志文件,此时会调用ClfsDecodeBlockPrivate函数,将签名缓冲区数据写回到扇区最后2字节。
3、当_CLFS_CONTAINER_CONTEXT结构的cidQueue字段值为-1时,会调用CClfsBaseFilePersisted::RemoveContainer函数。
4、RemoveContainer函数调用CClfsBaseFilePersisted::FlushImage函数将扇区最后2字节写入到pCaontainer字段。
5、当RemoveContainer函数调用完FlushImage函数后,pCaontainer值被替换为指定的代码地址,随后会将该地址作为虚表指针并调用相关函数。因为pCaontainer是可控的,因此可以执行任意构造的代码,从而实现权限提升。
风险分析:
此漏洞是一个本地漏洞,攻击者需要在计算机上获得访问权限,才能利用此漏洞。如果攻击者不能访问计算机,则他们不能利用此漏洞。
攻击者可以利用此漏洞在受感染的计算机上执行任意代码,绕过安全限制并获得系统管理员权限,以便进行高级攻击活动。
对于没有安装CLFS驱动程序的系统,该漏洞并不会对其造成影响。此外,该漏洞需要攻击者已经拥有系统上的普通用户权限才能被利用,因此加强用户账户的安全性也是非常重要的。
风险等级:【高危】:7.8
检查方式:
1.确认系统是否受到漏洞影响:查看系统是否使用了CLFS驱动程序,如果系统中没有此驱动程序,则不受此漏洞的影响。可以使用命令行工具"driverquery.exe"来列出系统中所有的驱动程序。
2.查找是否存在漏洞利用程序:分析系统中的日志,查找是否有异常的行为,例如安装了未知的软件或服务。同时,检查是否存在任何新的本地管理员账户。
3.更新操作系统:检查是否有适用于该漏洞的安全更新,如果有,应该尽快安装更新。
4.应用最佳实践:应该遵循操作系统的最佳安全实践,例如定期更新操作系统、使用最小权限原则,以及禁用不必要的服务和功能。
5.加强安全防范:加强系统的安全防护,例如使用防火墙、安装杀毒软件和防恶意软件工具等,防止未授权的访问或攻击。
修复方案:
在CClfsBaseFilePersisted::LoadContainerQ函数执行开始加入了CClfsBaseFile::ValidateRgOffsets函数以此检测SignaturesOffset字段是否与其他上下文相交。
一个新的逻辑块已添加到LoadContainerQ:
...
containerArray = (_DWORD *)((char *)BaseLogRecord + 0x328); // *CLFS_CONTAINER_CONTEXT->rgContainers
...
v22 = CClfsBaseFile::ContainerCount(this);
...
while ( containerIndex < 0x400 )
{
v17 = (CClfsContainer *)containerIndex;
if ( containerArray[containerIndex] )
++v24;
v89 = ++containerIndex;
}
...
if ( v24 == v22 )
{
if ( (unsigned int)Feature_Servicing_38197806__private_IsEnabled() )
{
v25 = (_OWORD *)((char *)v19 + 0x138);
v26 = (unsigned int *)operator new(0x11F0ui64, PagedPool);
rgObject = v26;
if ( !v26 )
{
goto LABEL_135;
}
memmove(v26, containerArray, 0x1000ui64);
v28 = rgObject + 0x400;
v29 = 3i64;
...
v20 = CClfsBaseFile::ValidateRgOffsets(this, rgObject);
v72 = v20;
operator delete(rgObject);
}
事实上,这个块是一个包装器CClfsBaseFile::ValidateRgOffsets:(检查签名偏移是否与任何上下文对象相交。此外,它还验证几个上下文字段,例如CLFS_NODE_ID)
__int64 __fastcall CClfsBaseFile::ValidateRgOffsets(CClfsBaseFile *this, unsigned int *rgObject)
{
...
LogBlockPtr = *(_QWORD *)(*((_QWORD *)this + 6) + 48i64); // * _CLFS_LOG_BLOCK_HEADER
...
signatureOffset = LogBlockPtr + *(unsigned int *)(LogBlockPtr + 0x68); // PCLFS_LOG_BLOCK_HEADER->SignaturesOffset
...
qsort(rgObject, 0x47Cui64, 4ui64, CompareOffsets); // sort rgObject array
while ( 1 )
{
currObjOffset = *rgObject2; // obtain offset from rgObject
if ( *rgObject2 - 1 <= 0xFFFFFFFD )
{
pObjContext = CClfsBaseFile::OffsetToAddr(this, currObjOffset); // Obtain in-memory representation
// of the object's context structure
...
unkn = currObjOffset - 0x30;
v13 = rgIndex * 4 + v5 + 0x30;
if ( v13 < v5 || v5 && v13 > unkn )
break;
v5 = unkn;
if ( *pObjContext == 0xC1FDF008 ) // CLFS_NODE_TYPE_CLIENT_CONTEXT
{
rgIndex = 0xC;
}
else
{
if ( *pObjContext != 0xC1FDF007 ) // CLFS_NODE_TYPE_CONTAINER_CONTEXT
return 0xC01A000D;
rgIndex = 0x22;
}
criticalRange = &pObjContext[rgIndex]; // get the address of context + 0x30
if ( criticalRange < pObjContext || (unsigned __int64)criticalRange > signatureOffset ) // comapre with sig offset
break;
}
++i;
++rgObject2;
if ( i >= 0x47C )
return ret;
}
return 0xC01A000D;
}
建议用户尽快安装适当的安全补丁,以修复这个漏洞。如果暂时无法安装补丁,建议用户使用以下步骤加强安全措施:
修复建议:
根据系统安装链接内相应补丁。另外:
1.限制访问计算机的用户,并确保仅授予必要的访问权限。
2.防止攻击者鱼塘式攻击,使用最新的反病毒软件和防火墙技术保护计算机。
3.在敏感数据上加密存储,以防止数据泄露。
4.使用最新的操作系统,软件和硬件,因为这些是对安全性最新漏洞可能会修复的要素。
5.定期进行安全审查和升级,以确保计算机和数据得到最新的保护和防护。
3.CVE-2022-37969 Windows 通用日志文件系统驱动程序特权提升漏洞
发现厂商:安恒信息,Mandiant.CrowdStrike, Zscaler
严重级别:严重 CVSS:7.8
被利用级别:检测到利用/很有可能被利用
0x01 简介:。
漏洞概述:该漏洞位于Common Log File System (CLFS)中,可导致认证攻击者以提升权限执行代码。通过越界写入实现提权。由于缺乏对 CLFS.sys 中基本日志文件 (BLF) 的基本记录头中的字段 cbSymbolZone 的严格边界检查。 cbSymbolZone存在一个名为CLFS_BASE_RECORD_HEADER的结构体。如果字段 cbSymbolZone 设置为无效偏移量,则会在无效偏移量处发生越界写入。该漏洞源于一个名为基本记录的元数据块,该元数据块存在于基本日志文件中,该数据块是在使用 CreateLogFile() 函数创建日志文件时生成的。
基本记录包含存储与基本日志文件相关的各种客户端、容器和安全上下文的信息的符号表。因此,通过特制的基本日志文件成功利用 CVE-2022-37969 可能会导致内存损坏,进而以可靠的方式引发系统崩溃(又名蓝屏死机或BSoD)。
漏洞描述:(分析步骤)
cbSymbolZone存在一个名为CLFS_BASE_RECORD_HEADER的结构体,其中就包括了一个名为cbSymbolZone的成员。在IDA中搜索该成员名字,可以找到唯一个操作该成员的函数CClfsBaseFilePersisted::AllocSymbol:如果字段 cbSymbolZone 设置为无效偏移量,则会在无效偏移量处发生越界写入。
可以看出,在这里cbSymbolZone成员的作用是标识在BLF文件中,CLFS_BASE_RECORD_HEADER后所有符号表的路径字符串(符号)总共占用了多少空间.在内存中,当新增添加一个Container时,使用该成员用来计算新增的Container应该跳过多少空间往后排(即跳过现有的最后一个container的路径字符串),并申请sizeof(CLFSHASHSYM)也就是0x30大小空间,然后清零这片内存.但该函数直接使用使用了来自BLF文件中存储的cbSymbolZone的值,该值可以是大于0、小于到结尾的任意值。举例来说就是,它可以让跳过的空间很少,从而改写了现有最后一个container路径字符串,更小则可清零container结构本身的pcontainer指针。pcontainer是内核态指针位于ffff000000000000000区域,如果高20位被清零,那截断后的地址低12指针地址就可以指向用户态可申请内存地址,VirtualAlloc申请0x10000000大小内存后就可以完全覆盖这片地址.由于pcontainer指针总是一0x10字节对其的,可以采用替换虚表函数为SeSetAccessStateGenericMapping方法利用。
利用的方式也是一样修改Thread的PreviousMode字段,这个函数作用就是对rcx+8的地址自增实现相同的效果,所以只要把所有的每0x10个字节把+0处填为虚表,+8处填为PreviousMode地址就可以了.在这里有一个需要绕过的地方就是CClfsContainer::Close处。
pcontainer指针总是一0x10字节对齐的,所以我们无法预估pcontainer+20和+30的检测点字段可能会出现在什么位置,走到CClfsContainer::Close里面和之前的一样函数利用的话就会因为要关闭的句柄不是合法的出错,一种可行的方案是DeleteLogByHandle或者原文的NtSetInformationFile->FileDispositionInformation方式将file->clientshuwdown_15c设为0x10这样就会走到上方伪代码的第一个if入口在里面直接调用container的虚表函数,从而绕过了CClfsContainer::Close检查.这里有个限制DeleteLogByHandle会对CreateLogFile的参数进行检查只要赋予所有读写权限GENERIC_ALL即可绕过这个检查。
影响版本:
0x04 环境搭建
0x05 漏洞复现
1.创建主blf文件通过CreateLogFile并关闭文件
2.创建一大堆的blf文件,通过查询BigPoolInformation确保最后申请的2个blf文件的MetadataBlock相隔距离正好为0x11000字节,并关闭最后申请的2个blf文件
3.篡改主blf文件MetaBlockScratch内容为以下代码,包括构造FAKE_CLIENT_CONTEXT
PCLFS_LOG_BLOCK_HEADER hd = (PCLFS_LOG_BLOCK_HEADER)(pFileBuff + 0x800);
hd->SignaturesOffset = 0x50;
ULONGLONG base_record_ptr = (ULONGLONG)hd + hd->RecordOffsets[0];
PCLFS_BASE_RECORD_HEADER base_record = PCLFS_BASE_RECORD_HEADER(base_record_ptr);
base_record->cbSymbolZone = 0x11000+0x15b;
base_record->rgClients[0] = ClientOffset;
PCLFS_CLIENT_CONTEXT fakectx = (PCLFS_CLIENT_CONTEXT)(base_record_ptr + ClientOffset);
PCLFSHASHSYM fakesym = (PCLFSHASHSYM)(base_record_ptr + ClientOffset - sizeof(CLFSHASHSYM));
fakesym->cbSymName = ClientOffset + sizeof(CLFS_CLIENT_CONTEXT);
fakesym->cbOffset = ClientOffset;
fakectx->cidNode.cType = 0xC1FDF007;
fakectx->cidNode.cbNode = sizeof(CLFS_CLIENT_CONTEXT);
fakectx->cidClient = 0;
fakectx->Reserved1 = 0;
fakectx->fAttributes = 0x0100;
fakectx->eState = CLFS_LOG_SHUTDOWN;
4.重新打开修改后主blf文件通过CreateLogFile
5.创建副blf文件通过CreateLogFile
6.对副blf文件调用DeleteLogByHandle或者原文的NtSetInformationFile->FileDispositionInformation切换析构模式
7.对副blf文件添加容器通过AddContainer
8.对主blf文件添加容器通过AddContainer
9.关闭所有句柄触发漏洞
利用步骤具体分析:
CLFS的元数据块总数默认为6个也就是如下的元数据类型,对于不是这个常量的元数据数量可以产生漏洞CVE-2022-3022详见看雪分析,在ReadImage先读取第一个ClfsMetaBlockControl元数据块,该块默认大小为400不能任意更改,读取后获取控制块中的所有元数据块配置数组,根据元数据块配置CLFS_METADATA_BLOCK读取和它之后的影子块保存在pbImage字段中,这个字段仅内核模式可见不写入文件中
数据块类型 | 元数据块类型 | 描述 |
Control Record | Control Metadata Block | 包含了有关布局(layout)、扩展(extend)区域以及截断(truncate)区域的信息 |
Base Record | General Metadata Block | 包含了符号表信息,其中包括该BLF有关的客户端、容器和安全上下文信息 |
Truncate Record | Scratch Metadata Block | 包含了因为截断操作而需要对扇区进行更改的客户端信息,以及具体更改的扇区字节. |
对于打开和创建的blf文件,在之后内存池空间没有被占位的情况下,ClfsMetaBlockGeneral和下个blf文件的ClfsMetaBlockGeneral几个间隔块大小经调试得出的结构偏移是常量0x11000,微软为我们提供个一个用户态函数可以查询到SystemBigPoolInformation具体的堆地址和tag,代码如下.这个查询函数可以查询到完整的大块堆信息,只要在申请之前查询一次申请之后查询一次即可准确分析出当前元数据所在的内核堆地址.当申请占位的文件最后堆块两个间隔正好是0x11000时,关闭这个两个占位文件,那些接下来申请的两个主文件中的ClfsMetaBlockGeneral也会出现在这个位置。
也就是说将cbSymbolZone设置为偏移0x11000加上pcontainer指针在元素据块中的偏移就能清空下个blf文件pcontainer指针的高位部分,实现和上个漏洞相同的利用结果.但是这里有个限制需要绕过,也就是cbSymbolZone需要小于SignaturesOffset,为了绕过这个限制,原文采用了一个巧妙的方法也就是构造一个特定的CLIENT_CONTEXT,在打开blf文件时如果在CClfsLogFcbPhysical::Initialize判断eState = CLFS_LOG_SHUTDOWN就会进入就会调用ClfsLogFcbPhysical::ResetLog。
CLIENT_CONTEXT这里lsnRestart_58所在元数据位置正好是第4个Signatures要写入的位置,被替换成了CLFS_LSN_INVALID也就是FFFFFFFF00000000重叠部分正好是FFFFFFFF。
CVE-2021-24521只是修复了Signatures与符号表相交的情况,并没有限制SignaturesOffset可以与自身的偏移量0x68重叠的情况,这里将SignaturesOffset设置为0x50那么第4个Signatures所在位置0x19FE(0xC*0x200+0x1FE)就会写入SignaturesOffset在文件中的偏移量68加上2的高16位也就是FFFF刚才提到的重叠部分的数据,这个利用方式很巧妙笔者还未发现被微软完全修复.SignaturesOffset被替换后的值变成了0xFFFF0050,从而绕过了cbSymbolZone限制使其可以越界清空下个堆块数据。
但是这种利用方式比较复杂,需要精确控制堆申请的偏移量,实际环境利用难度过高,其实还有一个更简单的利用方式,这种方式不需要清空下个堆块的pcontainer指针而是清空自己文件的指针,同样可以实现利用,实现方法只需要设置cbSymbolZone等于本文件的pcontainer指针偏移量+3就可以了,最后利用的效果是一样的。
测试方法:
- 创建初始 BLF 日志文件
- 创建多个随机 BLF 日志文件
- 创建初始日志文件
- 执行受控Heap Spray堆喷射
- 准备方法 CreatePipe() / NtFsControlFile()
- 一旦内存准备好,将触发漏洞
- 读取系统令牌
- 验证令牌
- 用系统覆盖我们进程的令牌
- 将流程作为系统执行
- 反转补丁:分析结构
- 损坏“pContainer”指针
- 重新访问补丁
- 损坏签名偏移量
- 破坏更多values
- 控制允许读取系统令牌的功能
- 编写流程来实现本地权限提升
风险分析:
这种漏洞通常封装在某些社工攻击形式下,如说服某人打开文件或点击链接,之后更多代码以提升后的权限执行以接管系统。通常对于某exploit会如何被广泛地利用我们所知甚少。
系统崩溃只是利用漏洞产生的结果之一,因为它也可以被武器化以实现特权升级。
攻击者必须已经拥有访问权限并能够在目标系统上运行代码,才能利用该漏洞进行提权操作,如果攻击者在目标系统上还没有这种能力,该漏洞无法实现远程执行代码。
风险等级:
【高危】:7.8
修复方案:及时打补丁。
ClfsBaseFilePersisted::LoadContainerQ 中添加了一个检查。
4.cve-2017-11176 linux kernel UAF
0x01 简介:
System V消息队列是采用轮询(polling)的方式,很浪费CPU。而 Posix 消息队列允许异步事件通知,当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程。这种异步事件通知调用mq_notify()函数实现,mq_notify()为指定队列建立或删除异步通知(当一个消息被放入某个空队列时,通知有两种方式,一是产生一个信号来通知,二是创建一个线程来执行特定程序,完成消息处理)。由于mq_notify()函数在进入retry流程时没有将sock指针设置为NULL,导致UAF漏洞。
UAF漏洞:UAF 漏洞是当我们 free 掉某个指针变量所指向的堆块的时候,未将该指针变量置0,导致该指针依然指着该堆块地址,当我们引用该指针的话,也就引用该指针所所指向的地址。这个漏洞对于开发者很容易忽略,但威力非常强大。条件竞争:在多线程的环境下,当多个线程同时访问某一个共享代码、变量或文件的时候,就有可能发生条件竞争的漏洞,利用该漏洞可以产生意想不到的效果,不过有时候需要碰撞该漏洞才行,有一定失败几率。(在linux kernel pwn里面一般开了多线程就很有可能是利用条件竞争)。cred:当我们fork一个新的进程的时候会产生cred结构体,在task_struct中大小为0xa8,注意当cred的uid,gid为0的话,我们就提权成功。
漏洞概述:Linux内核中的POSIX 消息队列实现中存在一个UAF漏洞CVE-2017-11176。攻击者可以利用该漏洞导致拒绝服务或执行任意代码。
使用多线程对 netlink_socket 进行操作时,若子线程在某一刻关闭了文件描述符,导致主线程sock引用计数出现错误,引发UAF。在Linux中,由于Linux自身实现了对象引用计数的一系列函数(例如),一旦出现代码逻辑错误(常见的是多线程竞争的情况),就容易导致UAF、Double-Free漏洞。CVE-2017-11176 的利用非常复杂,需要查看源码并构造结构绕过检查。主要步骤是,首先利用sendmsg 增加 sk_rmem_alloc,使mq_notify()中的netlink_attachskb()返回1,从而顺利进入mq_notify()函数中的retry代码;然后,构造主、子线程,触发mq_notify()中的Double-Free漏洞;接着,利用sendmsg堆喷射,并利用setsockopt()阻塞发送进程,使喷射块常驻于内存;最后,调用setsockopt,触发执行伪造函数 wait_queue_t.func,劫持控制流。
漏洞描述:(分析步骤)
mq_notify()函数:
- u_notification 为空时:调用remove_notification()撤销已注册的通知。
- u_notification 不为空:当前进程希望在有一个消息到达所指定的队列时得到通知,首先判断通知类型。(1)SIGV_THREAD:申请内存空间并将用户空间通知拷贝到内核(nc)-> 将nc压入sock队列中 -> 获取对应的fd -> 从fd对应的filp中获取对应的sock对象 -> 将数据包与sock相关联 -> 根据返回值选择continue/ goto retry / goto out -> goto retry:如果close这个file,那么将会直接goto out,此时sock不为空,会执行netlink_detachskb(),导致UAF。
如果我们创建A线程保持netlink_attachskb()返回1,并重复retry逻辑,这个时候sock的引用计数是保持平衡的,一加一减,但是sock并不为空。同时再创建B线程去关闭netlink socket对应的文件描述符。由于B线程关闭了netlink socket的文件描述符(由于close(fd)必须在setsockopt(fd)之前使用,所以套接字关闭后,需要一个新的套接字来使用。可采用dup()系统调用来复制,使两个文件描述符指向相同的file结构),在A线程还没跳到retry时,B线程关闭file,A在代码(4)处调用fdget时会失败,然后直接goto到out 代码,调用netlink_detachskb()进行释放(同时第2次调用sock_put(),引用计数减1,两次减1就是UAF)。
B线程close(fd)退出程序时,内核会自动将file对象的refcounter减1,并删除fd到file的映射(将fdt[fd]设置为null),最终会调用sock->ops->release() 来释放file结构;由于file对象被释放,它会删除相关sock的引用(即sock的引用计数将减1),sock引用计数减为0后也会被释放;但sock指针未清空,netlink_detachskb()又进行了二次释放,导致漏洞。这个漏洞是属于条件竞争型的Double-Free漏洞(竞争窗口—netlink_attachskb()和fget()之间)。如果这块内存又被我们申请回来,并写入其他数据控制程序流,导致uaf,就可以执行任意代码。
崩溃原因说明:由于EXP调用了dup(),所以崩溃原因不同,调用close()不会真的释放netlink_sock对象(只是减少了一次引用)。netlink_detachskb()实际上删除netlink_sock的最后一个引用(并释放它)。最后,在程序退出期间触发释放后重用,退出时关闭“unblock_fd”文件描述符。
影响版本:
Linux 2.6.27~4.11.10
内核版本至最高Linux kernel through 4.11.9中的mq_notify函数在进入etry logic时不会将sock指针设置为NULL。实际上是由于竞争导致的Double-Free漏洞,但竞态的时间可以无限延长。在Netlink套接字的用户空间关闭期间,它允许攻击者导致UAF。
0x04 环境搭建
0x05 漏洞复现
总体目标是使netlink_attachskb()函数返回1,但该函数中有两个条件需要绕过。一是 sk->sk_rmem_alloc > sk->sk_rcvbuf,有两种方法,可以通过netlink_sendmsg()增加sk->sk_rmem_alloc的值(目标2),也可以通过sock_setsockopt()尽可能地减小sk->rcvbuf的值(目标4);二是需调用wake_up_interruptible()强行唤醒线程(目标5)。
1. 目标1:让netlink_attachskb()返回1,从而顺利进入retry代码。
再次看看netlink_attachskb()函数:
2. 目标2:方法1——通过netlink_sendmsg()增大sk->sk_rmem_alloc的大小(增加到133120字节以上),进入netlink_attachskb()中的if条件,返回1。(通过sendmsg()触发)
(2)netlink_skb_set_owner_r():假设if条件不通过,会执行本函数。
调用链:ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); -> netlink_sendmsg() -> netlink_unicast() -> netlink_attachskb() -> netlink_skb_set_owner_r()
3. 目标3:通过netlink_sendmsg()函数到达 netlink_skb_set_owner_r() 函数。
总结一下,通过netlink_sendmsg() 执行netlink_unicast()需满足条件:
msg->msg_flags != MSG_OOB
scm_send()返回值 = 0,分析scm_send()函数可知,只需要 msg->msg_controllen <= 0 即可。
msg_>msg_namelen 不为0
msg->msg_name->nl_family = AF_NETLINK
msg->msg_name->nl_groups = 0
msg->msg_name->nl_pid != 0,指向receiver套接字
sender套接字必须使用NETLINK_USERSOCK协议
msg->msg_iovlen = 1
msg->msg_iov是一个可读的用户态地址
msg->msg_iov->iov_len <= sk->sk_sndbuf-32 (len)
msg->msg_iov->iov_base是一个可读的用户态地址
4. 目标4(可选):方法2——通过sock_setsockopt()减小sk->sk_rcvbuf(减小到0以下)。
在setsockopt函数中,找到sock_setsockopt()函数中对sk->sk_rcvbuf的操作。但是,一般sk->sk_rcvbuf始终是一个>0的值,而sk_rmem_alloc有可能为0,所以无论怎么修改都很难满足条件,最好还是修改sk_rmem_alloc。
5. 目标5:直接调用wake_up_interruptible()强行唤醒线程。(setsockopt()触发)
调用链:setsockopt系统调用 -> netlink_setsockopt() -> wake_up_interruptible()
延长竞态窗口:延长netlink_attachskb()和fget()之间的时间,方法是在主线程(执行mq_otigy())运行5s之后再执行子线程来close(fd),再调用setsockopt()唤醒主线程。
测试方法:
风险分析:
风险等级:【高危】:
0x06 修复方式
修复方案:
Posix消息队列允许异步事件通知,当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程。这种异步事件通知调用mq_notify函数实现,mq_notify为指定队列建立或删除异步通知。由于mq_notify函数在进入retry流程时没有将sock指针设置为NULL,可能导致UAF漏洞。将sock设置为NULL即可。
漏洞所在代码/ipc/mqueue.c:补丁点在mqueue.c,并且只添加了一行。
- 有漏洞的代码存在于mq_notify
- 在retry的逻辑中有错误
- 在sock的计数器上有错误导致UAF
- 漏洞与已经关闭的fd的条件竞争有关
5漏洞复现java
5.1cve-2023-22501confluence越权
5.2spring4shell漏洞复现并利用
6漏洞复现net
6.1veeambackup cve-2022-26500
6.2sharepointcve-2019-0604复现
1.java内存马webshell;
参考冰蝎方式直接加载内存马
实现一句话内存马代理;