Windows Kernel Drive Vulnerability Research: CVE-2020-17087
Windows LPE CNG.sys CVE-2020-17087: root cause + patch analysis + reduction of crashing sample to be a 1 liner
The original report from Google:
The original crash sample contains many lines:
This is the end result of my work for “cve-2020-17087” (1 liner):
We use a function called BCryptSetContextFunctionProperty (later in the blog, we will take a detailed look at the function). For now, we google the function and go through the MSDN (https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptsetcontextfunctionproperty)
This function sets the value of a named property for a cryptographic function in an existing CNG context.
Let’s take a closer look at “
cbValue” (contains the size, in bytes, of the pbValue buffer) and “
pbValue” (the address of a buffer that contains the new property value), because we can set to write this function and try to put something malicious in
cbValue and crash the system (BSOD).
Now, let’s check the bug analysis shown below:
Now, we will look at the call stack. Starting with
BCryptSetContextFunctionsProperty, control is passed to the kernel code in
NtDeviceIoControlFile, jumps into KsecDispatch, then passes to CNG, and then crashes at the vulnerable function, only this time it was my data that caused the crash as shown below:
On October 22, 2020, Google’s security researchers published a security report on bug.chromium.org titled “Issue 2104: Windows Kernel
cng.sys pool-based buffer overflow,” roughly meaning in CNG A security vulnerability was found in .sys and
CNG.sys, a Windows driver whose IOCTL 0x390400 made a function vulnerable to 16-bit integer overflow. The main security problem function is
cng!CfgAdtpFormatPropertyBlock, and this function is not exported. The vulnerability exists in a Microsoft driver called
What is cng.sys:
Cryptography API: Next Generation (CNG) is the long-term replacement for
CryptoAPI. CNG is designed to be extensible at many levels and cryptography agnostic in behavior. CNG provides a set of APIs that can be used to easily encrypt and decrypt content to authorization principals across multiple computers and can also be used for importing and exporting keys such as (
The file cng.sys is located in the C:WindowsSystem32drivers folder.
Overview of pool overflow vulnerability in cng.sys (original exploit):
This vulnerability is an integer overflow and buffer vulnerability in the Windows kernel encryption module (
cng.sys). In the function
cng!CfgAdtpFormatPropertyBlock, after multiplying the second parameter (
srclen) is multiplied by 6, an overflow occurs, resulting in a subsequent application buffer. If it is too small, the kernel accesses invalid memory, causing BSOD.
This bug was posted by j00ru and provided a lot of useful information that we can use to track down this bug further. Specifically, the information he provided us includes: call stack, code snippets, and part of the !analyze command. Shown below is the call stack information we will use:
Setting Up kernel debugging:
bcdedit /debug on
bcdedit /set TESTSIGNING ON
bcdedit /dbgsettings net hostip:192.168.29.119 port:54444 key:22.214.171.124
Analysis and Debugging:
The First thing I always try to check is analysis crash with a debugger
!analyze -v . To get a better understanding, we try to analyze in
First, we load the drivers for debugging.
sxe ld cng.sys“
I will try to explain the value step by step:
The first step is to analyze the stack frame from the crash analysis. We can set a breakpoint on the first point
cng!CngDispatch so that when we can reach
cng!CfgAdtpFormatPropertyBlock. for that we need some kind of Advance Debugging.
This is the next-generation encryption driver for Windows.
PoC Context we will use “
ba e1 /p eprocess”
Before calling the
CngDeviceControl function, the main a2 IRP data is processed to obtain the In/Out buffer address and length, as well as the
IOCTL code and
RequestMode, which are sent to
CngDeviceControl as six parameters respectively. Once the breakpoint is triggered and we are currently in
CngDispatch, we can use the call tracing technique to dock to
cng!CngDeviceControl, which is the next item in the call stack.
After judging a large number of IOCTL codes I finally tracked down IOCTL code that was used in POC as shown below:
I was very curious to check the values of a1 a2 and a3, we can use the debugger for this as shown below:
Now, we know that a1 and a3 are pointers to the input buffer, and a2 seems to indicate the variable length, whose value is
0x2aab+0x1000. Now, I will move ahead to
cng!ConfigIoHandler_Safeguarded It also performs a series of checks and allocates two blocks of memory with
BCryptAlloc, and copies the contents of the input buffer into a newly allocated piece of memory.
This particular function –
IoUnpack_SG_ParamBlock_Header as shown below, looked interesting:
While looking back to PoC I found this strange number value
0x1a2b3c4d It turned out to be an important value because when it is replaced, the exploit will not work and will not reach the vulnerable code path.
The next step is
ConfigFunctionIoHandler function, which takes six arguments as shown below, which I can also identify by the debugger:
Let’s Look for the first register RCX which is 10400 set as the second DWORD of the PoC, R8 is set to size+1000, and other both RDX and R9 are some kinds of allocating buffers that store our data.
Now, I attempt to dissect why the
RCX=10400 is very important in PoC Because if the value of this register is not
0x10400, the path changes and we cannot reach the code path. Also, the other functions in the switch statement are of little use to us, at least from an exploit perspective.
Let’s try to check some functions from
bcrypt.h, which is more or less about encryption settings, such as creating contexts, deleting contexts, setting contexts, configuration, etc.
Later on, I was looking
ConfigurationFunctionIoHandler for some kind of function related to that so I got a condition as shown below (https://support.industry.siemens.com/forum/WW/en/posts/what-io-field-can-be-used-to-display-a-dw-number-16-bits/67549) When
0x10400 or 0x400 (1024), it can directly take us to (
Next, we go to the Vulnerable function and we hit function
Now the real fun starts here as shown below:
This is the vulnerable function that J00ru speaks of.
So, let’s look at this line ”
v8 = BCryptAlloc((unsigned __int16) (6 * a2));” for eg
6 * 0x2aab = 2the result 6* size of is forcibly converted to an unsigned 16-bit integer. When multiplied by 6, the overflow will be 2. The function will call
SkAllocatePool to dynamically apply for pool space in the type
NonPagedPoolNx pool. However, the number of do-while loops is the size (ie 0x2ab), writing 6 bytes to the pool space each time, eventually leading to out-of-bounds writing and triggering
I checked the changes before and after the function, and found that after the patch, the function calls the
cng!RtlUShortMult function to process the incoming size.
cng!RtlUShortMult function is very short and simple, and can judge the overflow of the result of size* 6 (compared with 0xffff) as shown below:
In my next blog post, we will take a close look at an LPE exploit with a Chrome RCE exploit.
Looking for more security research blog posts? click here
VS-Labs: Security Research
We Solve Complex Technical Challenges VerSprite’s Research and Development division (a.k.a VS-Labs) is comprised of individuals who are passionate about diving into the internals of various technologies. Our clients rely on VerSprite’s unique offerings of zero-day vulnerability research and exploit development to protect their assets from various threat actors. From advanced technical security training to our research for hire B.O.S.S offering, we help organizations solve their most complex technical challenges.