12 February 2011

Getting Hard disk drive info with DeviceIOControl

It’s a pretty popular question on forums – how do I get the serial number for a hard disk drive. The serial number is then used for some form of homebrew security. The consensus amongst those who’ve been programming a while is that it is a waste of time and effort, unreliable and not particularly good at securing your programs…

Anyway, as a fun challenge I had a go at getting the serial number from my laptops ATA-attached drive, by using DeviceIOControl to send the IDENTIFY DEVICE ATA command, as defined in the ATA spec. This won’t work on SCSI drives, or raid or …,… ,…, so it’s not reliable and isn’t the answer for those of you looking for a unique id (there isn’t a reliable unique id).

Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Imports System.Security
Imports System.ComponentModel
Imports System.Text
 
Module Module1
 
    <SuppressUnmanagedCodeSecurity()> _
    Private Class NativeMethods
        <DllImport("kernel32", SetLastError:=True)> _
        Public Shared Function CreateFile( _
         ByVal FileName As String, _
         ByVal DesiredAccess As Integer, _
         ByVal ShareMode As Integer, _
         ByVal SecurityAttributes As IntPtr, _
         ByVal CreationDisposition As Integer, _
         ByVal FlagsAndAttributes As Integer, _
         ByVal hTemplateFile As IntPtr) As SafeFileHandle
        End Function
 
        <DllImport("kernel32.dll", SetLastError:=True)> _
        Friend Shared Function DeviceIoControl( _
            ByVal deviceHandle As SafeFileHandle, _
            ByVal controlCode As Integer, _
            ByRef inBuffer As ATA_PASS_THROUGH_EX_WITH_BUFFERS, _
            ByVal inBufferSize As Integer, _
            ByRef outBuffer As ATA_PASS_THROUGH_EX_WITH_BUFFERS, _
            ByVal outBufferSize As Integer, _
            ByRef bytesReturned As Integer, _
            ByVal overlapped1 As IntPtr) As Boolean
        End Function
    End Class
 
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure ATA_PASS_THROUGH_EX
        Public Length As Short
        Public AtaFlags As Short
        Public PathId As Byte
        Public TargetId As Byte
        Public Lun As Byte
        Public ReservedAsUchar As Byte
        Public DataTransferLength As Integer
        Public TimeOutValue As Integer
        Public ReservedAsUlong As Integer
        Public DataBufferOffset As IntPtr
        <MarshalAs(UnmanagedType.ByValArray, sizeconst:=8)> _
        Public PreviousTaskFile() As Byte
        <MarshalAs(UnmanagedType.ByValArray, sizeconst:=8)> _
        Public CurrentTaskFile() As Byte
    End Structure
 
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure ATA_PASS_THROUGH_EX_WITH_BUFFERS
        Public Apt As ATA_PASS_THROUGH_EX
        <MarshalAs(UnmanagedType.ByValArray, sizeconst:=512)> _
        Public Data() As Byte
    End Structure
 
    Sub Main()
        Console.WriteLine("Enter a drive letter")
        Dim letter As Char = Console.ReadKey.KeyChar
        Console.WriteLine()
        Const GenericRead As Integer = &H80000000
        Const GenericWrite As Integer = &H40000000
        Const FileShareRead As Integer = 1
        Const FileShareWrite As Integer = 2
        Const OpenExisting As Integer = 3
        Dim drivePath As String = String.Concat("\\.\" & letter & ":")
        Console.WriteLine("Trying path: " & drivePath)
        Using driveHandle As SafeFileHandle = NativeMethods.CreateFile( _
         drivePath, _
         GenericRead Or GenericWrite, _
         FileShareRead Or FileShareWrite, _
         IntPtr.Zero, _
         OpenExisting, _
         0, _
         IntPtr.Zero)
            If driveHandle.IsInvalid Then
                Console.WriteLine("CreateFile ERROR: " & (New Win32Exception).Message)
                Console.ReadKey()
                Return
            End If
            Dim apex As New ATA_PASS_THROUGH_EX
            apex.Length = Marshal.SizeOf(apex)
            apex.AtaFlags = 2 ' ATA_FLAGS_DATA_IN
            apex.DataTransferLength = 512 ' The command returns a 512 byte package of info.
            apex.TimeOutValue = 10 ' 10 second timeout.
            apex.DataBufferOffset = Marshal.OffsetOf(GetType(ATA_PASS_THROUGH_EX_WITH_BUFFERS), "Data")
            apex.CurrentTaskFile = New Byte(7) {} ' This contains the command we are requesting.
            apex.CurrentTaskFile(6) = &HEC        ' <-- the command "IDENTIFY DEVICE"
            Dim apexb As New ATA_PASS_THROUGH_EX_WITH_BUFFERS
            apexb.Apt = apex
            Dim inBufferSize As Integer = Marshal.SizeOf(GetType(ATA_PASS_THROUGH_EX_WITH_BUFFERS))
            Dim bytesReturned As Integer
            Const IOCTL_ATA_PASS_THROUGH As Integer = &H4D02C
            Dim result As Boolean = NativeMethods.DeviceIoControl(driveHandle, IOCTL_ATA_PASS_THROUGH, _
                apexb, inBufferSize, apexb, inBufferSize, bytesReturned, IntPtr.Zero)
            If result = False Then
                Console.WriteLine("DeviceIOControl ERROR: " & (New Win32Exception).Message)
                Console.ReadKey()
                Return
            End If
            DumpString("S/N: ", apexb.Data, 20, 20)
            DumpString("Firmware: ", apexb.Data, 46, 8)
            DumpString("Model: ", apexb.Data, 54, 40)
            Console.WriteLine("Press any key")
            Console.ReadKey()
        End Using
    End Sub
 
    Private Sub DumpString(msg As String, bytes() As Byte, offset As Integer, length As Integer)
        ' The strings are slightly weird - endianness? If you use ASCII.GetBytes then each character 
        ' pair is reversed.
        Dim sb As New StringBuilder(msg & " '")
        For i As Integer = offset To offset + length - 1 Step 2
            sb.Append(Chr(bytes(i + 1)))
            sb.Append(Chr(bytes(i)))
        Next
        sb.Append("'"c)
        Console.WriteLine(sb.ToString)
    End Sub
 
End Module

Output on my laptop:

Enter a drive letter
c
Trying path: \\.\c:
S/N:  '            5TG077W9'
Firmware:  'DE14    '
Model:  'ST9160411ASG                            '
Press any key

4 comments:

  1. very usefull notes and we need more information on this topic please post updated data . thanks for your post.
    Dot net online tutorials


    ReplyDelete
  2. it's works perfect. thank you so much for sharing

    ReplyDelete
  3. Everything is fine, am happy about your blog. Thanks admin for sharing the unique content, you have done a great job I appreciate your effort and I hope you will get more positive comments from the web users.
    Hadoop Training in Chennai
    Hadoop Training
    Best Hadoop Training in Chennai
    Best Hadoop Training Institute in Chennai

    ReplyDelete