Showing posts with label read serial number. Show all posts
Showing posts with label read serial number. Show all posts

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

26 November 2008

SD Card - Read CID/Serial number - Musings

Well I discovered that my .Net app does actually work on 32 bit operating systems too. Assuming I'm reading the data correctly, I can read CIDs in XP SP2, on an old Asus W5 laptop. It also has an integral card reader. BUT: it didn't work at first. DeviceIOControl returned "Invalid Function" (Win32 Error code 1). I went in to device manager and changed the driver over:

1

TO:

2

So, It looks like the driver plays an important role too...

So to read the CID, so far I've found:

1) You can't get it if the reader is a USB device. 2) You can't get it if the driver doesn't like the DeviceIOControl call.

CreateFile path for the Volume:

There are 2 choices mentioned on forums/in documentation. "//./X:" or "//./PhysicalDriveX". There's another one with a guid too.

I've been using "//./X:" as it worked. Maybe using the other path to get the handle will make a difference on the machine where I can't get it to work:

C:\Users\SEP>wmic diskdrive get name,size,model Model Name Size FUJITSU MHW2160BJ G2 \\.\PHYSICALDRIVE0 160039272960 SD Memory Card \\.\PHYSICALDRIVE1 8225280

Edit: I'm now on about version 5 of my program and I'm using C#! So, I've moved from C++/CLI -> VB.Net -> C#... It now works on the machine I mentioned above, and I'm using the physicaldrive string. I'm trying to get Overlapped to work on x64 and x32, it seems there are some problems with NativeOverlapped. This will allow the call to read the CID and CSD to run on another thread, and the user to be notified when the results come back. (Reading the CID takes a noticable amount of time.) It requires a pointer, hence the move to C#.

15 November 2008

Read secure digital (SD) card serial number from the CID

(Quick link to an executable, this might read the serial number on Windows XP/Vista/32/64 when you are not using a USB card reader (so when you have the card in a slot on the side of a laptop it should work - though it depends on the driver!)). Requires .Net Framework 3.5.

I got an email about a post from ages ago on the msdn forums about reading the serial number from an SD Card. People want to do this as the number is factory stamped and unchangeable, so it could be used to protect programs from being copied. Distribute your program on an SD Card, have it check the serial, and have it fail if the number doesn't match a hard-coded version. Maybe there's more to it than that. But anyway the thread went along the lines of:

1) I want to read the serial 2) Someone suggested: Well use DeviceIOControl to send IOCTL_DISK_GET_STORAGEID to the drive.

At the time I'd just been playing with DeviceIOControl, and had spent ages working out how to use it from .Net. There weren't any useful examples that I could find. I was working at trying to make a CD Burner, using COM Interop to talk to IMAPI on XP (before IMAPI2). I'd learnt about DeviceIOControl, and how it can be used to send messages straight to a device. I used it to find out things that IMAPI couldn't tell you - like the exact media in the drive, and whether the drive was ready. I eventually got a vb.net program that could burn MP3s to CD. I never quite finished it though. I wasn't sure how to deal with all the possible errors during burning - there were so many!

I quickly wrote some code that demonstrated sending the message. I didn't know if the number was really the hardware stamped serial or whether it was just something windows dreamt up (like the volume serial you get with dir c:\).

I was asked again this week about this, and pointed in the direction of the concise SD specification (you can only see the big one if you pay $$$$, as it isn't an open standard). It's call the simplified version of the physical layer spec. This describes various commands that can be sent. Command 10 returns the CID. The CID is the bit we are interested in, the Card IDentification register:

Name Field Width CID-slice
Manufacturer ID MID 8 [127:120]
OEM/Application ID OID 16 [119:104]
Product Name PNM 40 [103:64]
Product Revision PRV 8 [63:56]
Product Serial Number PSN 32 [55:24]
reserved -- 4 [23:20]
Manufacturing Date MDT 12 [19:8]
CRC7 Checksum CRC 7 [7:1]
not used, always 1 -- 1 [0:0]

There we have 128 bits used to identify the device. I was asked if the number that my earlier program grabbed was this number from the CID. I don't know...(edit - NO it isn't the same. IOCTL_DISK_GET_STORAGEID is useless - it gets a serial that Windows invents when it formats a volume.)

I decided to try and get this CID number out, and I've managed to, but there are provisos. My program (VB.Net) currently works only on Vista 64. Weirdly it only works when compiled for the x86 platform (the build target in visual studio). When I tried it on Vista 32 I got invalid handle errors. Sheesh. The other biggy is that it only works when the SD Card is plugged into a reader that is attached directly to the pci bus. If USB is involved in the chain, then the DeviceIOControl messages go to the USB host controller, and it doesn't understand them. It works on my laptop's reader, but not on a fan controller/card reading gadget plugged in to my desktop as its card reader is attached via a USB header on the mobo.

Here are some pics of the CIDs. They came out reversed, and the endianness of the serial number and manufacturer date were unhelpful. In the pics below, the 16 bytes are the ones I got in my buffer, but reversed order. They tally quite well with the above table. But, the leftmost byte (0 in each case) shouldn't be there, and we are missing the CRC and final bit. So it goes - Unspecified byte, MID, OID, OID, PNM, PNM, PNM, PNM, PNM, PRV, PSN, reserved+MDT, MDT. And the CRC is missing.

CIDS

I bought the two Crucial 4GB cards at the same time, note the serial numbers are adrift by 2 - a good indication I'm not getting gobbledegook. The Manufacture date is mm/yyyy. Also the numbers tally with the hardware IDs found in Windows Device Manager, which are described in the DDK, er WDK rather. Which you can get for free now. I bought the Veho card two weeks ago, looks like it was made just last month. It also looks like Vehos are made by Corsair - or maybe they are both made by some other company. Crucial don't fill in the product name or version, all the bytes were 0.

The bit where is says "This is an SD card", is because it also checks if the drive selected in the combobox is an SD card before attempting to get data.

So, how's it done? Well, you send an IOCTL specifying an SD Command with DeviceIOControl. Again it's in the WDK. These are just user mode calls, so we aren't in the realms of writing a driver. The main difficulties were trying to decipher the documentation, which appears to be plain wrong in places. This lead to many many errors being returned by DeviceIOControl. Eventually I managed to send off command 0 without any complaint whatsoever, and figured I must be doing something right. I was using C++ code from elsewhere in the msdn forums and a bit of C++ Interop. I then got something back from cmd 10. I then went and did it in VB.Net instead as all this C++ was driving me mad. I can read it, but programming in it is a very laborious process for me.

I used IOCTL_SFFDISK_QUERY_DEVICE_PROTOCOL to check if a mount point was an SD card. Then IOCTL_SFFDISK_DEVICE_COMMAND to send cmd10. DeviceIOControl just takes the IOCTL code and a blob of memory - in .Net I used a byte array. The byte array contains a SFFDISK_DEVICE_COMMAND_DATA structure first, then the SDCMD_DESCRIPTOR. Both of these structures were 20 bytes big on x64 vista when using c++, so I made sure they were that size in .Net too. The Cmd field of SDCMD_DESCRIPTOR is very nasty. The documentation says to fill it with a value from the SD_COMMAND_CODE data type and lists two "allowed values". Well, both of those values give errors. Instead you should set the sd command code - i.e. 10 for command 10, 0 for command 0!!!!! After that structure I sent 16 bytes to receive the CID. And unfortunately I get 15 bytes of CID with a 00 in front and no CRC. Maybe I have some alignment problems with the structures. It might explain why my code isn't porting too.

I'll iron out the bugs and try and get it running on my dilapidated axim 50v too with the compact framework. I'm currently attempting to flash the rom on it to something newer. I nearly binned it last week in a grand tidy up. I'll have to download the academic version of Visual Studio, which is Pro, to do the windows mobile stuff. I've only got Standard (which was a MS freebie from the launch). Code to follow. (note there are other, newer, posts on this on the blog, and some other code)