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..
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:
Hide Copy Code
... 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.
Hide Shrink Copy Code
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.
Hide Copy Code
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
:
Hide Copy Code
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:
Hide Shrink Copy Code
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:
Hide Copy Code
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; }
No comments:
Post a Comment