Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# WriteFile() Stops Writing at Sector 242 on USB Drives

I wrote the below code to write 0xFF to all bytes on my USB storeage device. For some reason the WriteFile() calls begin to error out at sector 242. I have done this on two separate USB storage devices and then examined the devices in a hex editor. Sector 242 appears to be the start of the file allocation table on a FAT16 formated device and the start of the boot area on an NTFS device. I'm sure it is no conincidence that it is erroring out at these exact locations, however I do not know how to alter this behavior. The HRESULT I am receiving when the WriteFile fails is -2147024891 which is E_ACCESSDENIED. Does anyone know what could be causing the problem?

NOTE: IF you are going to run this code on your local system BE VERY CAREFUL as I have hardcoded the physical device ID for my USB device. Please be sure to update the deviceId variable with the device you are attempting to write to. You do not want to destroy your hard drive.

    public enum EMoveMethod : uint
    {
        Begin = 0,
        Current = 1,
        End = 2
    }

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern uint SetFilePointer([In] SafeFileHandle hFile, [In] long lDistanceToMove, [Out] out int lpDistanceToMoveHigh, [In] EMoveMethod dwMoveMethod);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

    [DllImport("kernel32", SetLastError = true)]
    internal extern static int ReadFile(SafeFileHandle handle, byte[] bytes, int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);

    [DllImport("kernel32.dll", SetLastError = true)]
    internal extern static int WriteFile(SafeFileHandle handle, byte[] bytes, int numBytesToWrite, out int numBytesWritten, IntPtr overlapped_MustBeZero);

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
    private static extern bool DeviceIoControl(SafeFileHandle hDevice, uint dwIoControlCode, byte[] lpInBuffer, int nInBufferSize, byte[] lpOutBuffer, int nOutBufferSize, out int lpBytesReturned, IntPtr lpOverlapped);

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
    private static extern bool CloseHandle(SafeFileHandle handle);

public void wipeDisk()
{
        const uint OPEN_EXISTING = 3;
        const uint GENERIC_WRITE = (0x40000000);
        const uint FSCTL_LOCK_VOLUME = 0x00090018;
        const uint FSCTL_UNLOCK_VOLUME = 0x0009001c;
        const uint FSCTL_DISMOUNT_VOLUME = 0x00090020;

        bool success = false;
        int intOut;
        string deviceId = @"\\.\PHYSICALDRIVE2";
        long DiskSize = 2056320000;

        SafeFileHandle diskHandle = CreateFile(deviceId, GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
        if (diskHandle.IsInvalid)
        {
            Console.WriteLine(deviceId + " open error.");
            return;
        }

        Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": opened.");

        success = DeviceIoControl(diskHandle, FSCTL_LOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero);
        if (!success)
        {
            Console.WriteLine(deviceId + " lock error.");
            CloseHandle(diskHandle);
            return;
        }

        Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": locked.");

        success = DeviceIoControl(diskHandle, FSCTL_DISMOUNT_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero);
        if (!success)
        {
            Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": dismount error.");
            DeviceIoControl(diskHandle, FSCTL_UNLOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero);
            CloseHandle(diskHandle);
            return;
        }

        Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": unmounted.");

        int numBytesPerSector = 512;
        long numTotalSectors = DiskSize / 512;

        byte[] junkBytes = new byte[512];
        for (int x = 0; x < 512; x++)
        {
            junkBytes[x] = 0xFF;
        }

        for (long sectorNum = 0; sectorNum < numTotalSectors; sectorNum++)
        {
            int numBytesWritten = 0;
            int moveToHigh;

            uint rvalsfp = SetFilePointer(diskHandle, sectorNum * numBytesPerSector, out moveToHigh, EMoveMethod.Begin);

            Console.WriteLine("File pointer set " + Marshal.GetHRForLastWin32Error().ToString() + ": " + (sectorNum * numBytesPerSector).ToString());

            int rval = WriteFile(diskHandle, junkBytes, junkBytes.Length, out numBytesWritten, IntPtr.Zero);

            if (numBytesWritten != junkBytes.Length)
            {
                Console.WriteLine("Write error on track " + sectorNum.ToString() + " from " + (sectorNum * numBytesPerSector).ToString() + "-" + moveToHigh.ToString() + " " + Marshal.GetHRForLastWin32Error().ToString() + ": Only " + numBytesWritten.ToString() + "/" + junkBytes.Length.ToString() + " bytes written.");
                break;
            }
            else
            {
                Console.WriteLine("Write success " + Marshal.GetHRForLastWin32Error().ToString() + ": " + numBytesWritten.ToString() + "/" + junkBytes.Length.ToString() + " bytes written.");
            }
        }

        success = DeviceIoControl(diskHandle, FSCTL_UNLOCK_VOLUME, null, 0, null, 0, out intOut, IntPtr.Zero);
        if (success)
        {
            Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": unlocked.");
        }
        else
        {
            Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": unlock error: " + Marshal.GetHRForLastWin32Error().ToString());
        }

        success = CloseHandle(diskHandle);
        if (success)
        {
            Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": handle closed.");
        }
        else
        {
            Console.WriteLine(deviceId + " " + Marshal.GetHRForLastWin32Error().ToString() + ": close handle error: " + Marshal.GetHRForLastWin32Error().ToString());
        }
}

EDIT/UPDATE

I was able to get this to work successfully after doing a low level wipe of the USB device using a third-party tool. After the drive was completely zeroed out I was able to write to the device successfully. It seems as if windows is locking the device as soon as it recognizes a valid fat or ntfs file system and the usage of

    const uint FSCTL_LOCK_VOLUME = 0x00090018;
    const uint FSCTL_DISMOUNT_VOLUME = 0x00090020;

paired with DeviceIoControl does not seem to override the lock windows has on the device.

Does anyone know how to successfully lock a removable USB Device in windows using DeviceIoControl on a drive that has a valid file system?

I have used several third-party tools that do what I am trying to do and they work successfully. I know that it is possible but all of the MSDN documentation that I have read has not helped to solve the problem.

EDIT/UPDATE 2

This is taken from https://web.archive.org/web/20130507212546/http://msdn.microsoft.com/en-us/library/ff551353.aspx

The application needs to lock the volume, dismount the volume, or both, before it can issue DASD I/O. This is new to Windows Vista and was done to address potentially malicious techniques.

  1. The file system will block all write operations to reserved sections of the disk. In this case, those reserved sections include the MBR and the two FAT areas. To block these areas, you need to lock the volume by sending FSCTL_LOCK_VOLUME. You must issue this structure on the same volume handle that performs the actual write operations. This request can fail if there are open file handles. In this case, the application can force a dismount of the file system by issuing FSCTL_DISMOUNT_VOLUME. However, the volume is not actually dismounted until the file handle is closed. Until then, the application can continue to issue DASD I/O by using the same file handle that is currently open.

  2. There is an extended region beyond the volume space that is known to the file system where write operations will be blocked. To allow write operations to this region, you must issue FSCTL_ALLOW_EXTENDED_DASD_IO on the volume handle.

You can use the Win32 API routine DeviceIoControl to issue all the previous FSCTSs.

I believe this is exactly what we are implementing in the above code but it does not appear to be working correctly. We are getting a handle and are locking and dismounting the device so we should be able to write to the protected area correct?

EDIT/UPDATE 3

Ok this is the current order of opening disks and volumns.. The methods for locking, dismounting, etc work just the order we think is wrong..

SafeFileHandle volumeHandle = CreateFile("\\.\E:",...);
LockVolume(volumeHandle);
DismountVolume(volumeHandle);
SafeFileHandle diskHandle = CreateFile("\\.\PHYSICALDRIVE1"...);
WriteStuff(diskHandle);
//Fails...
UnlockVolume(volumeHandle);
CloseVolume(volumeHandle);
CloseDisk(diskHandle);

I am still getting the same results, it only works whenever the disk is trashed.

like image 981
Brandon Stout Avatar asked Aug 22 '12 21:08

Brandon Stout


3 Answers

There is a confusion between disk and drive here.

If you want full access to a disk (which is your case as you're using \\.\PHYSICALDRIVE), you must lock all mounted volumes, which are basically all partitions (i.e. drives) of your physical disk.

Instead of using FSCTL_LOCK_VOLUME on the handle returned by CreateFile("\\.\PHYSICALDRIVE"...), get a handle to each mounted volume (which is a drive, not a physical disk) using the string.Replace("\\\\.\\{0}:", DriveLetter) pattern.

You can get the list of mounted volumes (ultimately, you want a list of letters) for a given physical disk using IOCTL_DISK_GET_DRIVE_LAYOUT.


EDIT:

From MSDN :

A write on a disk handle will succeed if one of the following conditions is true:

The sectors to be written to do not fall within a volume's extents.

The sectors to be written to fall within a mounted volume, but you have explicitly locked or dismounted the volume by using FSCTL_LOCK_VOLUME or FSCTL_DISMOUNT_VOLUME.

The sectors to be written to fall within a volume that has no mounted file system other than RAW.

So basically, what you should do is:

  • get a handle to each of the volumes
  • use FSCTL_LOCK_VOLUME or FSCTL_DISMOUNT_VOLUME on each volume. If no file is being used in the volume (i.e. no opened handle by any process to any file), FSCTL_LOCK_VOLUME is enough
  • get a handle to the physical disk
  • write to the physical disk
  • close both handles. Closing the volume handle will release the lock.

Also make sure you're running your application with admin rights (elevated process).

like image 106
ken2k Avatar answered Sep 30 '22 00:09

ken2k


I'm guessing that you're using Windows Vista or later. The OS will block any attempts to direct write to those sectors, so you need to do a lock first. More on this here:

http://msdn.microsoft.com/en-us/library/ff551353.aspx

Also just checking in SO brought this post up:

CreateFile: direct write operation to raw disk "Access is denied" - Vista, Win7

The investigative information there might be helpful, HTH...

like image 34
code4life Avatar answered Sep 30 '22 02:09

code4life


EDIT

I have edited this answer to reflect ken2k's suggestions.

ken2k's suggestion did in fact fix the problem I was having. I am not sure why my previous attempts using that approach were unsuccessful, however I have just revisited/tweaked my code and that approach does appear to work correctly.

Here are the steps I used in order to solve this problem:

  • Obtain a handle to physical disk
  • Obtain a handle to each logical drive on the physical disk
  • Lock each drive on the physical disk
  • Dismount each drive on the physical disk
  • Lock the physical disk (Optional)
  • Dismount the physical disk (Optional)
  • Use the physical disk handle to zero out the entire physical disk
  • Unlock each logical drive
  • Unlock the physical disk (Only if you choose to lock the Disk)
  • Close the logical drive handles
  • Close the physical disk handle

NOTE: If you wish to do back-to-back disk operations without terminating your program and you have used the FSCTL_DISMOUNT_VOLUME functionality you will need to "remount" the disk using something similar to the following:

ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_DiskDrive");

or

System.IO.DriveInfo.GetDrives();

To map the logical drive ID's to the physical disk ID when you are attempting to lock each individual logical drive use the following piece of code to link the logical drive labels to the physical disk labels:

    List<string> driveLetters = new List<string>();
    string deviceId = @"\\.\PHYSICALDRIVE1";
    string queryString = "ASSOCIATORS OF {Win32_DiskDrive.DeviceID='" + deviceId + "'} WHERE AssocClass = Win32_DiskDriveToDiskPartition";
    ManagementObjectSearcher diskSearcher = new ManagementObjectSearcher("root\\CIMV2", queryString);
    ManagementObjectCollection diskMoc = diskSearcher.Get();
    foreach (ManagementObject diskMo in diskMoc)
    {
        queryString = "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" + diskMo["DeviceID"] + "'} WHERE AssocClass = Win32_LogicalDiskToPartition";
        ManagementObjectSearcher driveSearcher = new ManagementObjectSearcher("root\\CIMV2", queryString);

        ManagementObjectCollection driveMoc = driveSearcher.Get();
        foreach (ManagementObject driveMo in driveMoc)
        {
            driveLetters.Add("\\\\.\\" + driveMo["DeviceID"].ToString());
        }
    }

So for example, if the physical disk label is \\.\PHYSICALDRIVE1 and it contains one logical drive with the drive letter "E" the above code will map \\.\E: to \\.\PHYSICALDRIVE1.

As per ken2k's suggestion this mapping can also be done using the IOCTL_DISK_GET_DRIVE_LAYOUT functionality.

like image 40
Brandon Stout Avatar answered Sep 30 '22 00:09

Brandon Stout