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 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).
GeneralRe: Thoughtsmembersupercat927 Oct '08 - 7:26 
There are other concerns (like RAID) that hinder this sort of thing, so it's barely worthwhile.
 
There is no guarantee that overwriting a file will cause the data blocks that previously contained the file to be overwritten. Indeed, on many flash drives, a each time a file block is written it will likely be placed on different areas of flash. Thermite is a pretty reliable means of destroying information on a drive; software methods may work most of the time, but often cannot be very well guaranteed.
GeneralRe: ThoughtsmemberSteppenwolfe27 Oct '08 - 7:40 
This is consumer grade app, not for DOD, (where drive must be melted, pulverized, degaused etc). This is also written for use with disk drives, not flash, zip, or external media. If you want to write something more effective then this, you would have to use a driver that interfaced with the hard drive itself and used the drives firmware to run the erasure tasks directly. As for effectiveness of this app, show me a software recovery application that can recover valid data from a file shredded with NShred, (good luck).
GeneralRe: Thoughtsmembersupercat927 Oct '08 - 13:08 
Software-based recovery will usually be impossible after a file has been overwritten once. In those cases where it is not (most commonly because a defragmenter happened to move the file while leaving the old blocks unused), overwriting the file multiple times is unlikely to help.
 
A file shredder that worked with removable flash media would probably be even more useful than one for hard drives; I didn't notice anything in your article that said nShread was not suitable for such purposes, but deleting a small from a large flash drive, even with something like nShred, may be ineffective. It would have been helpful if the authors of the mass storage device specs had provided a means by which a device could be asked to erase all unused data blocks on an as-convenient basis (setting a flag in flash so that the request would survive a power-down and power-up) but I am unaware of any device which implements such a facility.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 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