Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Communication with USB Devices using HID Protocol

Rate me:
Please Sign up or sign in to vote.
4.84/5 (39 votes)
26 Sep 2023CPOL2 min read 181.1K   13.6K   143   61
This article will help you to understand how to communicate with the USB devices using WinAPI in C#
This article delves into the utilization of the HID protocol on Windows, allowing you to send and receive HID packets from any connected USB devices directly through an application, without the need for third-party DLLs

Warning

This USB sniffer, because of its user mode method access to hardware, cannot read HID packets with RID at 0, it's due to Windows protection level to prevent keyloggers/spying software.

Image 1

(Do not add 0x, else the application will crash since I haven't added 0x prefix support)

Background

This article was possible with this WDK sample:

Basically, it's just a rewrite of this sample, but in a simple form.

Using the Code

Main code:

C++
void    CheckHIDRead()
{
    HIDReadInfo.Device = new HID_DEVICE[DeviceDiscovery.FindDeviceNumber()];

    DeviceDiscovery.FindKnownHIDDevices(ref HIDReadInfo.Device);

    for (var Index = 0; Index < HIDReadInfo.Device.Length; Index++)
    {
        if (HIDReadInfo.VendorID != 0)
        {
            var Count = 0;

            if (HIDReadInfo.Device[Index].Attributes.VendorID == HIDReadInfo.VendorID)
            {
                Count++;
            }
            if (HIDReadInfo.Device[Index].Attributes.ProductID == HIDReadInfo.ProductID)
            {
                Count++;
            }

            if (Count == 2)
            {
                HIDReadInfo.iDevice = Index;
                HIDReadInfo.Active  = true;

                return;
            }
        }
    }
}
void    CheckHIDWrite()
{
    HIDWriteInfo.Device = new HID_DEVICE[DeviceDiscovery.FindDeviceNumber()];

    DeviceDiscovery.FindKnownHIDDevices(ref HIDWriteInfo.Device);

    for (var Index = 0; Index < HIDWriteInfo.Device.Length; Index++)
    {
        if (HIDWriteInfo.VendorID != 0)
        {
            var Count = 0;

            if (HIDWriteInfo.Device[Index].Attributes.VendorID == HIDWriteInfo.VendorID)
            {
                Count++;
            }
            if (HIDWriteInfo.Device[Index].Attributes.ProductID == HIDWriteInfo.ProductID)
            {
                Count++;
            }
            if (HIDWriteInfo.Device[Index].Caps.UsagePage == HIDWriteInfo.UsagePage)
            {
                Count++;
            }
            if (HIDWriteInfo.Device[Index].Caps.Usage == HIDWriteInfo.Usage)
            {
                Count++;
            }

            if (Count == 4)
            {
                HIDWriteInfo.iDevice = Index;
                HIDWriteInfo.Active  = true;

                return;
            }
        }
    }
}

CheckHIDRead() and CheckHIDWrite() are used for checking if we have press Read or Send button and if entered data (VID-PID-Usa...) correspond to a connected USB device.

This function returns the number of USB HIDs in order to scan them.

C++
Int32   FindDeviceNumber()
{
    var hidGuid        = new Guid();
    var deviceInfoData = new SP_DEVICE_INTERFACE_DATA();

    HidD_GetHidGuid(ref hidGuid);

    //
    // Open a handle to the plug and play dev node.
    //
    SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
    hardwareDeviceInfo    = SetupDiGetClassDevs(ref hidGuid, IntPtr.Zero, 
                            IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    deviceInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));

    var Index = 0;
    while (SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, IntPtr.Zero, 
                                       ref hidGuid, Index, ref deviceInfoData))
    {
        Index++;
    }

    return (Index);
}

This function returns a data structure of each USB device needed for HIDRead() and HIDWrite().

C++
static public void FindKnownHIDDevices(ref HID_DEVICE[] HID_Devices)
{
    var hidGuid                 = new Guid();
    var deviceInfoData          = new SP_DEVICE_INTERFACE_DATA();
    var functionClassDeviceData = new SP_DEVICE_INTERFACE_DETAIL_DATA();

    Hid.HidD_GetHidGuid(ref hidGuid);

    //
    // Open a handle to the plug and play dev node.
    //
    SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
    hardwareDeviceInfo    = SetupDiGetClassDevs(ref hidGuid, IntPtr.Zero, IntPtr.Zero,
                                                DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    deviceInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));

    for (var iHIDD = 0; iHIDD < HID_Devices.Length; iHIDD++)
    {
        SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, IntPtr.Zero, ref hidGuid, iHIDD, ref deviceInfoData);

        //
        // Allocate a function class device data structure to receive the
        // goods about this particular device.
        //
        var RequiredLength = 0;
        SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, ref deviceInfoData,
                                        IntPtr.Zero, 0, ref RequiredLength, IntPtr.Zero);

        if (IntPtr.Size == 8)
        {
            functionClassDeviceData.cbSize = 8;
        }
        else if (IntPtr.Size == 4)
        {
            functionClassDeviceData.cbSize = 5;
        }

        //
        // Retrieve the information from Plug and Play.
        //
        SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, ref deviceInfoData,
                                        ref functionClassDeviceData, RequiredLength,
                                        ref RequiredLength, IntPtr.Zero);

        //
        // Open device with just generic query abilities to begin with
        //
        OpenHIDDevice(functionClassDeviceData.DevicePath, ref HID_Devices, iHIDD);
    }
}

This function extend FindKnownHIDDevices().

C++
static void OpenHIDDevice(String DevicePath, ref HID_DEVICE[] HID_Device, Int32 iHIDD)
{
    //
    // RoutineDescription:
    // Given the HardwareDeviceInfo, representing a handle to the plug and
    // play information, and deviceInfoData, representing a specific hid device,
    // open that device and fill in all the relivant information in the given
    // HID_DEVICE structure.
    //
    HID_Device[iHIDD].DevicePath = DevicePath;

    //
    // The hid.dll api's do not pass the overlapped structure into deviceiocontrol
    // so to use them we must have a non overlapped device.  If the request is for
    // an overlapped device we will close the device below and get a handle to an
    // overlapped device
    //
    CloseHandle(HID_Device[iHIDD].Handle);
    HID_Device[iHIDD].Handle     = CreateFile(HID_Device[iHIDD].DevicePath,
                                              FileAccess.ReadWrite, FileShare.ReadWrite,
                                              0, FileMode.Open, FileOptions.None,
                                              IntPtr.Zero);
    HID_Device[iHIDD].Caps       = new HIDP_CAPS();
    HID_Device[iHIDD].Attributes = new HIDD_ATTRIBUTES();

    //
    // If the device was not opened as overlapped, then fill in the rest of the
    // HID_Device structure.  However, if opened as overlapped, this handle cannot
    // be used in the calls to the HidD_ exported functions since each of these
    // functions does synchronous I/O.
    //
    Hid.HidD_FreePreparsedData(ref HID_Device[iHIDD].Ppd);
    HID_Device[iHIDD].Ppd = IntPtr.Zero;

    Hid.HidD_GetPreparsedData(HID_Device[iHIDD].Handle, ref HID_Device[iHIDD].Ppd);
    Hid.HidD_GetAttributes   (HID_Device[iHIDD].Handle, ref HID_Device[iHIDD].Attributes);
    Hid.HidP_GetCaps         (HID_Device[iHIDD].Ppd   , ref HID_Device[iHIDD].Caps);

    var Buffer = Marshal.AllocHGlobal(126);
    {
        Hid.HidD_GetManufacturerString(HID_Device[iHIDD].Handle, Buffer, 126);
        HID_Device[iHIDD].Manufacturer = Marshal.PtrToStringAuto(Buffer);

        Hid.HidD_GetProductString(HID_Device[iHIDD].Handle, Buffer, 126);
        HID_Device[iHIDD].Product = Marshal.PtrToStringAuto(Buffer);

        Hid.HidD_GetSerialNumberString(HID_Device[iHIDD].Handle, Buffer, 126);
        HID_Device[iHIDD].SerialNumber = Marshal.PtrToStructure<Int32>(Buffer);
    }
    Marshal.FreeHGlobal(Buffer);
}

Then come two important functions that will make you able to read or send HID packets between a USB HID and a PC.

C++
static async public void BeginAsyncRead(object? state)
{
    //
    // Read what the USB device has sent to the PC and store the result inside HID_Report[]
    //
    if (HIDReadInfo.Active == true)
    {
        var Device       = HIDReadInfo.Device[HIDReadInfo.iDevice];
        var ReportBuffer = new Byte[Device.Caps.InputReportByteLength];

        if (ReportBuffer.Length > 0)
        {
            await Task.Run(() =>
            {
                var NumberOfBytesRead = 0U;
                Kernel32.ReadFile(Device.Handle, ReportBuffer, Device.Caps.InputReportByteLength, ref NumberOfBytesRead, IntPtr.Zero);
            });

            HIDReadInfo.ReportData = new List<Byte>(ReportBuffer);
        }
    }
}

static public void BeginSyncSend(object? state)
{
    //
    // Sent to the USB device what is stored inside WriteData[]
    //
    if (HIDWriteInfo.Done is false && HIDWriteInfo.Active is true)
    {
        var Device = HIDWriteInfo.Device[HIDWriteInfo.iDevice];
        var ReportBuffer = new Byte[Device.Caps.OutputReportByteLength];

        if (ReportBuffer.Length > 0)
        {
            //
            // Add ReportID to the first byte of HID_ReportContent
            //
            ReportBuffer[0] = HIDWriteInfo.ReportID;

            //
            // Copy ReportData into HID_ReportContent starting from index 1
            //
            Array.Copy(HIDWriteInfo.ReportData, 0, ReportBuffer, 1, ReportBuffer.Length - 1);

            var varA = 0U;
            Kernel32.WriteFile(Device.Handle, ReportBuffer, Device.Caps.OutputReportByteLength, ref varA, IntPtr.Zero);
        }

        HIDWriteInfo.Done = true;
    }
}

To be able to do that, you'd need to set the following data before:

  • VendorID
  • ProductID
  • UsagePage
  • Usage
  • ReportID

But be careful, you'd need to set the correct values for all those parameters, if one is false, you will not be able to send HID packets.

To read HID packets, you just need:

  • VendorID
  • ProductID

Also, you cannot read if the USB device cannot send data and you cannot write if the USB device cannot read data (defined inside its HID Report Descriptor).

A device is defined by its VendorID:ProductID but shrunk into several functions defined by its UsagePage, Usage and ReportID.

As an example, the first function of a mouse is to send coordinate data, so you can read data from PC and the second function is to receive mouse button customization data, so you can send data from PC.

And to set those variables, you need to read the HID Descriptor of the USB devices that you target, it can be retrieved with a USB sniffer as https://github.com/djpnewton/busdog or http://www.usblyzer.com/usb-analysis-features.htm

The HID Descriptor usually start with 0x05, 0x01.

And to learn to read HID Descriptor, use this tool: http://www.usb.org/developers/hidpage#HID Descriptor Tool and https://docs.kernel.org/hid/hidintro.html

Because this code is just a rewrite of an old C code from the 90s, it works on all Windows Versions.

History

  • 2nd October, 2019: Initial version
  • 10th September, 2023: Project upgrade from Windows Forms .NET framework to WPF .NET Core

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
AnswerRe: And without using DLL, just an application is needed. Pin
Orphraie24-Nov-19 12:59
Orphraie24-Nov-19 12:59 
QuestionCrash Pin
Member 145750612-Sep-19 3:04
Member 145750612-Sep-19 3:04 
AnswerRe: Crash Pin
Orphraie17-Sep-19 13:16
Orphraie17-Sep-19 13:16 
AnswerRe: Crash Pin
Milton N26-Jul-20 17:18
Milton N26-Jul-20 17:18 
QuestionHid custon or Hid keyboard? Pin
Member 111704347-Apr-19 19:10
Member 111704347-Apr-19 19:10 
QuestionHid custon or Hid keyboard? Pin
Member 111704347-Apr-19 19:21
Member 111704347-Apr-19 19:21 
AnswerRe: Hid custon or Hid keyboard? Pin
Orphraie3-May-19 9:06
Orphraie3-May-19 9:06 
QuestionHid.Net Pin
TheChristian3-Jan-19 18:43
TheChristian3-Jan-19 18:43 
Hid.Net is much simpler and is cross platform.

Here is a sample:
Device.Net/Program.cs at master · MelbourneDeveloper/Device.Net · GitHub[^]

Sample code:
internal class TrezorExample : IDisposable
{
    #region Fields
    //Define the types of devices to search for. This particular device can be connected to via USB, or Hid
    private readonly List<FilterDeviceDefinition> _DeviceDefinitions = new List<FilterDeviceDefinition>
    {
        new FilterDeviceDefinition{ DeviceType= DeviceType.Hid, VendorId= 0x534C, ProductId=0x0001, Label="Trezor One Firmware 1.6.x", UsagePage=65280 },
        new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x534C, ProductId=0x0001, Label="Trezor One Firmware 1.6.x (Android Only)" },
        new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x1209, ProductId=0x53C1, Label="Trezor One Firmware 1.7.x" },
        new FilterDeviceDefinition{ DeviceType= DeviceType.Usb, VendorId= 0x1209, ProductId=0x53C0, Label="Model T" }
    };
    #endregion

    #region Events
    public event EventHandler TrezorInitialized;
    public event EventHandler TrezorDisconnected;
    #endregion

    #region Public Properties
    public IDevice TrezorDevice { get; private set; }
    public DeviceListener DeviceListener { get; private set; }
    #endregion

    #region Event Handlers
    private void DevicePoller_DeviceInitialized(object sender, DeviceEventArgs e)
    {
        TrezorDevice = e.Device;
        TrezorInitialized?.Invoke(this, new EventArgs());
    }

    private void DevicePoller_DeviceDisconnected(object sender, DeviceEventArgs e)
    {
        TrezorDevice = null;
        TrezorDisconnected?.Invoke(this, new EventArgs());
    }
    #endregion

    #region Public Methods
    public void StartListening()
    {
        TrezorDevice?.Dispose();
        DeviceListener = new DeviceListener(_DeviceDefinitions, 3000);
        DeviceListener.DeviceDisconnected += DevicePoller_DeviceDisconnected;
        DeviceListener.DeviceInitialized += DevicePoller_DeviceInitialized;
    }

    public async Task InitializeTrezorAsync()
    {
        //Get the first available device and connect to it
        var devices = await DeviceManager.Current.GetDevices(_DeviceDefinitions);
        TrezorDevice = devices.FirstOrDefault();
        await TrezorDevice.InitializeAsync();
    }

    public async Task<byte[]> WriteAndReadFromDeviceAsync()
    {
        //Create a buffer with 3 bytes (initialize)
        var writeBuffer = new byte[64];
        writeBuffer[0] = 0x3f;
        writeBuffer[1] = 0x23;
        writeBuffer[2] = 0x23;

        //Write the data to the device
        return await TrezorDevice.WriteAndReadAsync(writeBuffer);
    }

    public void Dispose()
    {
        TrezorDevice?.Dispose();
    }
    #endregion
}


modified 16-Jan-19 1:56am.

AnswerRe: Hid.Net Pin
Orphraie17-Jan-19 14:37
Orphraie17-Jan-19 14:37 
QuestionCode does not work! Pin
uniyalsearch3-Jan-19 17:16
uniyalsearch3-Jan-19 17:16 
AnswerRe: Code does not work! Pin
Orphraie1-Oct-19 22:32
Orphraie1-Oct-19 22:32 
QuestionBag ? Pin
Jerzy Brzeski7-Nov-18 5:27
Jerzy Brzeski7-Nov-18 5:27 
AnswerRe: Bag ? Pin
Orphraie17-Jan-19 14:50
Orphraie17-Jan-19 14:50 
QuestionHID over WiFi Pin
Mehrdad_K20-Aug-18 22:44
professionalMehrdad_K20-Aug-18 22:44 
AnswerRe: HID over WiFi Pin
Orphraie15-Oct-18 18:48
Orphraie15-Oct-18 18:48 
GeneralMy vote of 5 Pin
Robert_Dyball20-Jun-18 19:35
professionalRobert_Dyball20-Jun-18 19:35 
QuestionLooks very good... Pin
User 1106097919-May-18 4:47
User 1106097919-May-18 4:47 
AnswerRe: Looks very good... Pin
Orphraie15-Oct-18 18:50
Orphraie15-Oct-18 18:50 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.