Click here to Skip to main content
Click here to Skip to main content

Secure File Shredder

By , 28 Oct 2008
 

Introduction

This article describes a C style shredder in C# .NET.
Pizza van with tinted windows parked across the road for days? Strange clicking sound on the landline? Crooked CEO maybe? Then this is exactly the tool you have been searching for..

Background

I originally published v1 in VB6 as 'SDS' on Planet Source Code.

NShred 2.0

I decided to rewrite the SDS file shredder as one of my first forays into the C# .NET language, the reason being that it is a fairly compact and class-driven application. This time around, however, not being saddled with the limitations of the VB6 language, I was able to create a faster and more thorough application engine. The cShredder class is almost completely Win32 driven, using virtual memory buffers and the WriteFile API to overwrite the file with several passes of 0s, 1s, and random data. After some initial preamble of file path checks, attribute stripping, and enabling key access tokens within the process, we create the buffer:

...
hFile = CreateFileW(pName, GENERIC_ALL, FILE_SHARE_NONE, 
        IntPtr.Zero, OPEN_EXISTING, WRITE_THROUGH, IntPtr.Zero);
// get the file size
nFileLen = fileSize(hFile);
if (nFileLen > BUFFER_SIZE)
    nFileLen = BUFFER_SIZE;
if (hFile.ToInt32() == -1)
    return false;
// set the table
SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
pBuffer = VirtualAlloc(IntPtr.Zero, nFileLen, MEM_COMMIT, PAGE_READWRITE);
if (pBuffer == IntPtr.Zero)
    return false;
// fill the buffer with zeros
RtlZeroMemory(pBuffer, nFileLen);
...

Once the buffer is allocated and written to, call the overwriteFile method that uses WriteFile to overwrite the contents in buffered 'chunks'. Note that the file was opened with the WRITE_THROUGH flag, which causes the file to be written through the buffers and straight to disk. Also, all APIs used are of the 'W' flavor, so the shredder should be fully Unicode compliant.

private Boolean overwriteFile(IntPtr hFile, IntPtr pBuffer)
{
    UInt32 nFileLen = fileSize(hFile);
    UInt32 dwSeek = 0;
    UInt32 btWritten = 0;

    try
    {
        if (nFileLen < BUFFER_SIZE)
        {
            SetFilePointerEx(hFile, dwSeek, IntPtr.Zero, FILE_BEGIN);
            WriteFile(hFile, pBuffer, nFileLen, ref btWritten, IntPtr.Zero);
        }
        else
        {
            do
            {
                SetFilePointerEx(hFile, dwSeek, IntPtr.Zero, FILE_BEGIN);
                WriteFile(hFile, pBuffer, BUFFER_SIZE, ref btWritten, IntPtrZero);
                dwSeek += btWritten;
            } while ((nFileLen - dwSeek) > BUFFER_SIZE);
            WriteFile(hFile, pBuffer, (nFileLen - dwSeek), ref btWritten, IntPtr.Zero);
        }
        // reset file pointer
        SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
        // add it up
        if ((btWritten + dwSeek) == nFileLen)
            return true;
        return false;
    }
    catch
    {
        return false;
    }
}

The buffers are filled first with zeros, then ones, random data, then zeros again; this ensures that even with the most sophisticated software techniques, using a modern hard drive, all data will be rendered permanently unreadable. The random data phase uses the Crypto API to fill the buffer; intended for secure key creation, it also works well in this implementation.

private Boolean randomData(IntPtr pBuffer, UInt32 nSize)
{
    IntPtr iProv = IntPtr.Zero;

    try
    {
        // acquire context
        if (CryptAcquireContextW(ref iProv, "", MS_ENHANCED_PROV, 
            PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != true)
            return false;
        // generate random block
        if (CryptGenRandom(iProv, nSize, pBuffer) != true)
            return false;
        return true;
    }
    finally
    {
        // release crypto engine
        if (iProv != IntPtr.Zero)
            CryptReleaseContext(iProv, 0);
    }
}

One thing that most open source shredders I have seen fail to do, is to verify the read on the file. This is accomplished by comparing the buffer with the file contents using RtlCompareMemory:

private Boolean writeVerify(IntPtr hFile, IntPtr pCompare, UInt32 pSize)
{
    IntPtr pBuffer = IntPtr.Zero;
    UInt32 iRead = 0;

    try
    {
        pBuffer = VirtualAlloc(IntPtr.Zero, pSize, MEM_COMMIT, PAGE_READWRITE);
        SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
        if (ReadFile(hFile, pBuffer, pSize, ref iRead, IntPtr.Zero) == 0)
        {
            if (InError != null)
                InError(004, "The file write failed verification test.");
            return false; // bad read
        }
        if (RtlCompareMemory(pCompare, pBuffer, pSize) == pSize)
            return true; // equal
        return false;
    }
    finally
    {
        if (pBuffer != IntPtr.Zero)
            VirtualFree(pBuffer, pSize, MEM_RELEASE);
    }
}

After the overwrite cycles, the file is then zero sized ten times, and renamed thirty times:

private Boolean zeroFile(IntPtr pName)
{
    for (Int32 i = 0; i < 10; i++)
    {
        IntPtr hFile = CreateFileW(pName, GENERIC_ALL, FILE_SHARE_NONE,
            IntPtr.Zero, OPEN_EXISTING, WRITE_THROUGH, IntPtr.Zero);
        if (hFile == IntPtr.Zero)
            return false;
        SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
        // unnecessary but..
        FlushFileBuffers(hFile);
        CloseHandle(hFile);
    }
    return true;
}

private Boolean renameFile(string sPath)
{
    string sNewName = String.Empty;
    string sPartial = sPath.Substring(0, sPath.LastIndexOf(@"\") + 1);
    Int32 nLen = 10;
    char[] cName = new char[nLen];
    for (Int32 i = 0; i < 30; i++)
    {
        for (Int32 j = 97; j < 123; j++)
        {
            for (Int32 k = 0; k < nLen; k++)
            {
                if (k == (nLen - 4))
                    sNewName += ".";
                else
                    sNewName += (char)j;
            }
            if (MoveFileExW(sPath, sPartial + sNewName, 
		MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0)
                sPath = sPartial + sNewName;
            sNewName = String.Empty;
        }
    }
    // last step: delete the file
    if (deleteFile(sPath) != true)
        return false;
    return true;
}

For the truly paranoid user, there is a hidden startup switch, '/p', that enables the paranoid mode. With this setting, after the overwrite cycles, the files object identifier is deleted, effectively orphaning the file from the file and security subsystems:

private Boolean orphanFile(IntPtr pName)
{
    UInt32 lpBytesReturned = 0;
    IntPtr hFile = CreateFileW(pName, GENERIC_WRITE, FILE_SHARE_NONE,
        IntPtr.Zero, OPEN_EXISTING, WRITE_THROUGH, IntPtr.Zero);
    if (DeviceIoControl(hFile, FsctlDeleteObjectId, IntPtr.Zero, 
        0, IntPtr.Zero, 0, out lpBytesReturned, IntPtr.Zero))
        return false;
    return true;
}

History

  • 26th October, 2008: Initial post
  • 27th October, 2008: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Steppenwolfe
Network Administrator
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
AnswerRe: Shadow CopiesmemberSteppenwolfe2 Nov '08 - 1:57 
Word processors like MS 'Word' do make backups of the file (usually to the same directory). It is up to you to find them and delete them seperately.
GeneralRe: Shadow CopiesmemberTobiasP2 Nov '08 - 2:21 
Of course, but shadow copies are different. The copies you are talking about are created by the application, have different file names and are visible when the folder is browsed (though temporary files could be hidden). Shadow copies are a feature of the NTFS file system, are created by the operating system and have the same name as the current file. They are used, e.g., by backup software. You can think of shadow copies as a simple versioning system.
GeneralRe: Shadow CopiesmemberSteppenwolfe3 Nov '08 - 6:02 
If you are talking about VSS, then you would have to implement a provider to access the file in the shadowed volume. Afaik this is SDK, and requires a kernal mode IO filter driver, certainly not possible in C#. Native implementation though, syncs regularly, and provided that sys restore or other backup facility is not engaged, file remnants will be purged from the volume automatically.
AnswerRe: Shadow CopiesmemberKeith Vinson4 Nov '08 - 5:10 
Hi Steppenwolfe,
Not to harp on the point, but in order to feel secure in the usage of your delete strategy, you really do need to address the issue of shadow copies. They are backup copies created and maintained by the OS. They are very much like "point in time" or continuous backups that many RAID controllers can support. Basically since every time you write to disk all data contained within a given sector will be fully rewritten. So when the disk controller goes to rewrite the file it uses different sectors to store the newly updated sectors in the file. Keeping the previous sectors (and the corresponding sector link map) as the backup of the previous version of the file. This kind of stuff runs at a very low level and therefore might be out of your control...
 
Keith
GeneralRe: Shadow CopiesmemberSteppenwolfe4 Nov '08 - 15:49 
It certainly is possible to both identify and remove files within the VSS metadata, but if you examine the api, many of the functions list as 'reserved for system use', which means kernal mode access is required. I haven't really looked, but I am guessing that examples of api use are rarefied, and a lot of trial and error would be involved. Point being that it can not be done in C#, (at least not entirely).
As a test though, I deleted a number of files with this shredder, and ran system restore on the drive and.. files were still gone. I know this shredder goes as far as sdelete to shred a file, and probably as far as most commercial shredders, and at this point, that is all I was aiming for..
 
John
QuestionEver looked at SDelete?memberAlois Kraus28 Oct '08 - 21:37 
The SysInternals tool SDelete should do a much better job and it does comply with DOD regulations as well.
 
http://technet.microsoft.com/en-us/sysinternals/bb897443.aspx
 
Yours,
Alois Kraus
AnswerRe: Ever looked at SDelete?memberSteppenwolfe29 Oct '08 - 7:56 
When I wrote the first version of this in vb, I saw sdelete, but can't find the code online anymore. As for doing a better job? This works exactly like sdelete, using buffers to overwrite the file, only this goes further by zeroing the file contents after the overwite stages, and verifying the file write (which as I recall sdelete did not do), this also resets the file attributes, and uses two more overwrite passes then sdelete did. It also orphans the file by deleting its objectid, uses crypto fill, deletes the folders, etc. So.. makes me wonder why people are bombing it with poor votes, maybe they're not even looking at code?
GeneralRe: Ever looked at SDelete?memberAlovera20 May '09 - 8:37 
Nice code. I appreciate your thouroughness - this topic seems to generate quite of a bit of discussion! Wink | ;)
As a side note, you can get the source code for SDelete from the following archive site:
 
http://web.archive.org/web/20060427210141/www.sysinternals.com/Utilities/SDelete.html[^]
GeneralThoughtsmemberPIEBALDconsult26 Oct '08 - 16:35 
Have you read this[^]?
 

"After the overwrite cycles, the file is then zero sized ten times, and renamed thirty times:"
 
Probably needless.
 

There are other concerns (like RAID) that hinder this sort of thing, so it's barely worthwhile.
At any rate, C# (and VB) aren't the best choices (as you demonstrated by "almost completely Win32 driven").
I chose C when I wrote a file deleter using the tips in the above article, it's portable.
GeneralRe: Thoughts [modified]memberSteppenwolfe26 Oct '08 - 16:54 
Actually I have read that article in the course of my research for this project. The Gutmann method is however, obsolete. From what I gather most modern drives(>4G) require only a single overwrite to make the data unreadable. This is due to more accurate read/write heads with no space between tracks, (older dives could leave behind traces of data after an erasure, 'ghosting' due to magnetic drift to the space between tracks). Overwriting the file any more then what I've demonstrated here probably would be pointless, (and even my methods are likely overkill).
As for the language of choice, I find C# to be a reasonable choice, I could have just as easily written it in C/C++, but I don't see the boundry in using api, just a little extra work writing the declares. I do find C# a little overmanaged, but that is easy to navigate around.
As for a raid volume, though file may be distributed between disks, zeroing its size works in the same way as on a single disk, the space is simply reallocated in the mft (array controller is working at a layer beneath the OS).

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 28 Oct 2008
Article Copyright 2008 by Steppenwolfe
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid