NTFS Remote Code Execution (CVE-2020-17096) Analysis

This blog analyzes the CVE-2020-17096 vulnerability and provides a PoC exploit resulting in denial of service.

January 5 2021 by

Jamf Threat Labs

This is an analysis of the CVE-2020-17096 vulnerability published by Microsoft on December 12, 2020. The remote code execution vulnerability assessed with Exploitation: “More Likely”, grabbed our attention among the last Patch Tuesday fixes.

Diffing ntfs.sys

Comparing the patched driver to the unpatched version with BinDiff, we saw that there’s only one changed function, NtfsOffloadRead.

Comparing the patched driver to the unpatched version with BinDiff, showing only one changed function: NtfsOffloadRead

The function is rather big, and from a careful comparison of the two driver versions, the only changed code is located at the very beginning of the function:

Showing the changed code at the beginning of the NtfsOffloadRead function

Triggering the vulnerable code

From the name of the function, we deduced that it’s responsible for handling offload read requests, part of the Offloaded Data Transfers functionality introduced in Windows 8. An offload read can be requested remotely via SMB by issuing the FSCTL_OFFLOAD_READ control code.

Indeed, by issuing the FSCTL_OFFLOAD_READ control code we’ve seen that the NtfsOffloadRead function is being called, but the first if branch is skipped. After some experimentation, we saw that one way to trigger the branch is by opening a folder, not a file, before issuing the offload read.

Exploring exploitation options

We looked at each of the two changes and tried to come up with the simplest way to cause some trouble to a vulnerable computer.

First change: The NtfsExtendedCompleteRequestInternal function wasn’t receiving the IrpContext parameter.

Briefly looking at NtfsExtendedCompleteRequestInternal, it seems that if the first parameter is NULL, it’s being ignored. Otherwise, the numerous fields of the IrpContext structure are being freed using functions such as ExFreePoolWithTag. The code is rather long and we didn’t analyze it thoroughly, but from a quick glance we didn’t find a way to misuse the fact that those functions aren’t being called in the vulnerable version. We observed, thought, that the bug causes a memory leak in the non-paged pool which is guaranteed to reside in physical memory.

We implemented a small tool that issues offload reads in an infinite loop. After a couple of hours, our vulnerable VM ran out of memory and froze, no longer responding to any input. Below you can see the Task Manager screenshots and the code that we used.

Second change: An IRP pointer field, part of IrpContex, was set to NULL.

From our quick attempt, we didn’t find a way to misuse the fact that the IRP pointer field is set to NULL. If you have any ideas, let us know.

What about remote code execution?

We’re curious about that as much as you are. Unfortunately, there’s a limited amount of time that we can invest in satisfying our curiosity. We went as far as finding the vulnerable code and triggering it to cause a memory leak and an eventual denial of service, but we weren’t able to exploit it for remote code execution.

It is possible that there’s no actual remote code execution here, and it was marked as such just in case, as it happened with the “Bad Neighbor” ICMPv6 Vulnerability (CVE-2020-16898). If you have any insights, we’d be happy to hear about them.

CVE-2020-17096 PoC: Denial of Service

Before. An idle VM with a standard configuration and no running programs.

After. The same idle VM after triggering the memory leak, unresponsive.

C# code that causes the memory leak and the eventual denial of service. This was used with the Windows Protocol Test Suites.

Subscribe to the Jamf Blog

Have market trends, Apple updates and Jamf news delivered directly to your inbox.

To learn more about how we collect, use, disclose, transfer, and store your information, please visit our Privacy Policy.