diff --git a/enumerator/syscall_windows.go b/enumerator/syscall_windows.go index 1dbcd9c..2c6b379 100644 --- a/enumerator/syscall_windows.go +++ b/enumerator/syscall_windows.go @@ -38,6 +38,7 @@ func errnoErr(e syscall.Errno) error { var ( modsetupapi = windows.NewLazySystemDLL("setupapi.dll") + modcfgmgr32 = windows.NewLazySystemDLL("cfgmgr32.dll") procSetupDiClassGuidsFromNameW = modsetupapi.NewProc("SetupDiClassGuidsFromNameW") procSetupDiGetClassDevsW = modsetupapi.NewProc("SetupDiGetClassDevsW") @@ -46,6 +47,10 @@ var ( procSetupDiGetDeviceInstanceIdW = modsetupapi.NewProc("SetupDiGetDeviceInstanceIdW") procSetupDiOpenDevRegKey = modsetupapi.NewProc("SetupDiOpenDevRegKey") procSetupDiGetDeviceRegistryPropertyW = modsetupapi.NewProc("SetupDiGetDeviceRegistryPropertyW") + procCM_Get_Parent = modcfgmgr32.NewProc("CM_Get_Parent") + procCM_Get_Device_ID_Size = modcfgmgr32.NewProc("CM_Get_Device_ID_Size") + procCM_Get_Device_IDW = modcfgmgr32.NewProc("CM_Get_Device_IDW") + procCM_MapCrToWin32Err = modcfgmgr32.NewProc("CM_MapCrToWin32Err") ) func setupDiClassGuidsFromNameInternal(class string, guid *guid, guidSize uint32, requiredSize *uint32) (err error) { @@ -136,3 +141,27 @@ func setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, prop res = r0 != 0 return } + +func cmGetParent(outParentDev *devInstance, dev devInstance, flags uint32) (cmErr cmError) { + r0, _, _ := syscall.Syscall(procCM_Get_Parent.Addr(), 3, uintptr(unsafe.Pointer(outParentDev)), uintptr(dev), uintptr(flags)) + cmErr = cmError(r0) + return +} + +func cmGetDeviceIDSize(outLen *uint32, dev devInstance, flags uint32) (cmErr cmError) { + r0, _, _ := syscall.Syscall(procCM_Get_Device_ID_Size.Addr(), 3, uintptr(unsafe.Pointer(outLen)), uintptr(dev), uintptr(flags)) + cmErr = cmError(r0) + return +} + +func cmGetDeviceID(dev devInstance, buffer unsafe.Pointer, bufferSize uint32, flags uint32) (err cmError) { + r0, _, _ := syscall.Syscall6(procCM_Get_Device_IDW.Addr(), 4, uintptr(dev), uintptr(buffer), uintptr(bufferSize), uintptr(flags), 0, 0) + err = cmError(r0) + return +} + +func cmMapCrToWin32Err(cmErr cmError, defaultErr uint32) (err uint32) { + r0, _, _ := syscall.Syscall(procCM_MapCrToWin32Err.Addr(), 2, uintptr(cmErr), uintptr(defaultErr), 0) + err = uint32(r0) + return +} diff --git a/enumerator/usb_windows.go b/enumerator/usb_windows.go index 24be4e1..f8e86a5 100644 --- a/enumerator/usb_windows.go +++ b/enumerator/usb_windows.go @@ -62,6 +62,11 @@ func parseDeviceID(deviceID string, details *PortDetails) { //sys setupDiOpenDevRegKey(set devicesSet, devInfo *devInfoData, scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (hkey syscall.Handle, err error) = setupapi.SetupDiOpenDevRegKey //sys setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, bufSize uint32, reqSize *uint32) (res bool) = setupapi.SetupDiGetDeviceRegistryPropertyW +//sys cmGetParent(outParentDev *devInstance, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Parent +//sys cmGetDeviceIDSize(outLen *uint32, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Device_ID_Size +//sys cmGetDeviceID(dev devInstance, buffer unsafe.Pointer, bufferSize uint32, flags uint32) (err cmError) = cfgmgr32.CM_Get_Device_IDW +//sys cmMapCrToWin32Err(cmErr cmError, defaultErr uint32) (err uint32) = cfgmgr32.CM_MapCrToWin32Err + // Device registry property codes // (Codes marked as read-only (R) may only be used for // SetupDiGetDeviceRegistryProperty) @@ -194,14 +199,46 @@ func (set devicesSet) destroy() { setupDiDestroyDeviceInfoList(set) } +type cmError uint32 + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx type devInfoData struct { size uint32 guid guid - devInst uint32 + devInst devInstance reserved uintptr } +type devInstance uint32 + +func cmConvertError(cmErr cmError) error { + if cmErr == 0 { + return nil + } + winErr := cmMapCrToWin32Err(cmErr, 0) + return fmt.Errorf("error %d", winErr) +} + +func (dev devInstance) getParent() (devInstance, error) { + var res devInstance + errN := cmGetParent(&res, dev, 0) + return res, cmConvertError(errN) +} + +func (dev devInstance) GetDeviceID() (string, error) { + var size uint32 + cmErr := cmGetDeviceIDSize(&size, dev, 0) + if err := cmConvertError(cmErr); err != nil { + return "", err + } + buff := make([]uint16, size) + cmErr = cmGetDeviceID(dev, unsafe.Pointer(&buff[0]), uint32(len(buff)), 0) + if err := cmConvertError(cmErr); err != nil { + return "", err + } + return windows.UTF16ToString(buff[:]), nil +} + type deviceInfo struct { set devicesSet data devInfoData @@ -291,6 +328,20 @@ func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails) er } parseDeviceID(deviceID, details) + // On composite USB devices the serial number is usually reported on the parent + // device, so let's navigate up one level and see if we can get this information + if details.IsUSB && details.SerialNumber == "" { + if parentInfo, err := device.data.devInst.getParent(); err == nil { + if parentDeviceID, err := parentInfo.GetDeviceID(); err == nil { + d := &PortDetails{} + parseDeviceID(parentDeviceID, d) + if details.VID == d.VID && details.PID == d.PID { + details.SerialNumber = d.SerialNumber + } + } + } + } + /* spdrpDeviceDesc returns a generic name, e.g.: "CDC-ACM", which will be the same for 2 identical devices attached while spdrpFriendlyName returns a specific name, e.g.: "CDC-ACM (COM44)", the result of spdrpFriendlyName is therefore unique and suitable as an alternative string to for a port choice */