Filling up NTFS partition forever without admin privileges

NTFS logo

NTFS is an advanced file system that is one of the main parts of all modern Windows operating system versions. This file system supports logging, it has the ability to recover data, advanced security, file streams and many other features. However, sometimes with rich features you get problems that were absent in older file systems like FAT32.

As you may already know, NTFS allows to set security attributes for each file or directory: which users or user groups are able to read the file object and which to write; who isn't able to view the directory, and who is; how object accesses should be logged, etc. All this information is often duplicated: for example, the C:\Windows\System32 directory contains a lot of DLL files that are likely to have similar security attributes: the same users have the same access rights to each of these files. Of course, it is suboptimal to store all identifiers of these groups and users (SIDs) and security descriptors with access control lists (ACLs) for each separate file. NTFS developers came up with a smart solution and implemented it in NTFS v3.0 (Windows 2000): they moved all the information on security and access rights to a separate shared file called $Secure, or rather, to its data stream named $Secure:$SDS. This file is not accessible by Windows users and administrators and is a system file, which is serviced by the NTFS driver only. It is impossible to access it without special software.

How was this implemented? Very simple! For each file, instead of storing security data in the meta information of the file itself, the Security Id field has been added to the attribute $STANDARD_INFORMATION, which is always present for every file. This field refers to the corresponding security descriptor in the $Secure file. The $Secure file, in turn, contains a set of structures with the descriptors used. Thus, if you create a new file with default access rights (or, for example, copy the file), then its Security Id will be selected from the existing structures in the $Secure file. That is, if a descriptor that your file requires is already in $Secure, then a new one will not be created: the Security Id of an existing descriptor will simply be written to your file metadata. It saves some space: instead of copying the potentially large descriptor, only the Security Id reference (4 bytes) is copied. If you decide to change the security descriptor for the file by specifying a non-standard, previously unused descriptor, then a new entry with a new Security Id will be created in the $Secure file, and then this new Security Id will be written to your file metadata.

And here's the catch. Let’s think about what happens if you now delete the file (or change its security descriptor). Ideally, a record created exclusively for your file should be deleted from $Secure, because no file object uses it anymore. However, NTFS does not track which files or directories utilize the security descriptor. NTFS does not even count how many files or directories refer to a descriptor! Maybe we are wrong and missed something? Let's take a closer look at the structure of entries in $Secure:$SDS:

Offset Size Description
0x00 4 Security Descriptor Hash
0x04 4 Security Id
0x08 8 Offset of this entry from the beginning of the file
0x10 4 Size of this entry
0x14 V Security descriptor
0x14 + V - Alignment

No, there is definitely nothing here that could indicate which files and directories use the descriptor (or at least how many file objects use it). This page tells about the $Secure:$SDH and $Secure:$SII indices, but there is nothing useful there either.

So, the file system driver has no choice but to leave all the security descriptors in the $Secure file until better days (in case any file starts using some of them again). And this space will be taken up on your partition, and you will not be able to free it easily. You can defragment $MFT or even $Secure using an utility like Contig, but, as far as I know, there is no software that would remove unnecessary descriptors from $Secure. Wow, there is such software - CHKDSK (although it requires administrator privileges to run)! Let's imagine how this could be implemented:

  1. First we need to list ALL the file objects on the disk partition and save all the detected Security Ids somewhere in RAM.
  2. Next, we need to remove all the descriptors that we didn't find in the first stage from $Secure (from all its streams and indices).
  3. This may cause some other descriptors to move, and their Security Ids may change. So, we need to correct Security Ids of the affected files so that they refer to the correct descriptors. Moreover, we'll need to fix descriptor offsets in $Secure for the descriptors which have moved.

This is a lot of work that cannot be done simply and quickly. Most likely, you will need to unmount the disk partition and perform the cleanup when no one else uses it. In a word, it is difficult to implement, especially for an inexperienced user.

This immediately raises the following question: can an evil hacker bloat the $Secure file to such an extent that it takes up all the free space on the hard drive? Arranging system DoS in such clever way? As it turns out, he/she can, even without administrator privileges! Limited user rights are enough for this!

Let's write the code that does this. We'll create an empty file in the temporary directory accessible to us, and we'll constantly change its security descriptor. We will randomly generate each new descriptor so that it does not coincide with the existing ones. This will make the NTFS driver write our descriptors to $Secure, but it will not delete them, gradually eating up more and more space on the user's disk. The user will notice that the free space on the disk has disappeared, but it would be impossible to find any new files that occupy it. The user will not be able to free up space without reformatting the disk (or running CHKDSK with correct options), too!

Of course, we'll code in C++. Let's start by generating a random SID:

This code generates a random string of the S-1-5-21-X-Y​​ format. This is a typical identifier (SID) format for a user or a group of users. It may not exist, but NTFS doesn’t care much (after all, you could copy a file from somewhere else, where an identifier exists). Now we’ll write a function that converts this string to the SID structure and adds its access attributes to the EXPLICIT_ACCESS_W structure:

Now all we have to do is to write a function that fills the array of structures EXPLICIT_ACCESS_W and returns an array of their corresponding SIDs. We need to store all generated SIDs in memory until we add them to the file security descriptor.

Now let's write a couple of helper functions that will return the writable temporary directory path and prepare a temporary file in it:

Next, we need the function to open a file by its name. We will then add random security descriptors to this file:

Now we have everything we need to write the program core, which does everything that we planned. Let's write the main program function:

That's the whole code - only 130 lines (the full version download link is at the end of the post).

Now let’s try to run this program on a test virtual machine with Windows 10. Here is what I had before the program started:
Disk before filling

And here is what the partition looks like 20 minutes since program start:
Disk after filling

About 3.5 gigabytes of free space just evaporated. The partition fills quite slowly, but this happens quietly and, most importantly, does not require any admin privileges! The program utilizes about 20% of a single processor core. On a four or eight-core processor, it will be completely invisible among other running programs. In my test, the program was launched with limited user rights. Now, let's take a look at the $Secure file using OS Forensics software:
OS forensics $Secure:$SDS after disk filling

For comparison, here is what I had before my program had been launched:
OS forensics $Secure:$SDS before disk filling

Impressive! Missing 3.5 gigabytes are used by the $Secure file. As a bonus, this can slow down the file system, as the number of entries in $Secure has increased significantly. You can also look at the output of the WinDirStat program, which shows the total size of files and directories (by the way, a handy tool for deleting old unneeded large files). As you can see, it reports 46.8 GB is occupied (screenshots were taken after OS Forensics installation, so another chunk of space was used by this software):
WinDirStat after disk filling

Windows at the same time believes that 49 GB is actually occupied:
Used disk space after filling

And here are the security attributes of our temporary file:
Temp file security descriptor

In conclusion, let's think about what happens if the user is experienced enough and tries to figure out, what's going on. The user can try to study the NTFS log by opening it, for example, using the NTFS Journal Viewer utility by Orion Forensics.
NTFS journal after disk filling

Unfortunately, the user will be disappointed. There are only three suspicious entries regarding some temporary file. There are no records about thousands of changes to the file security descriptor, there are only a couple of them. It's possible to find them in a huge number of other entries, only if the user knows the file name in advance. Otherwise, it’s like to search for a needle in a haystack.

So what do we have? The developers tried to optimize NTFS, but they made a flaw, which ultimately can lead to slow system DoS even when the intruder doesn't possess administrator privileges.

Download the source code and the compiled exe file: NTFS fucker (archive password: kaimi).

Leave a Reply

Your email address will not be published.