UVC输入头描述符在usbvideo.sys中的校验
关于UVC输入头描述符的介绍可详见:https://www.usbzh.com/article/detail-98.html
在usbvideo.sys驱动中,调用IRP_MJ_PNP的回调函数USBVideoPnpStart调用StartUSBVideoDevice,该函数在获取的UVC相机的配配置描述符之后,调用DumpAndValidateAllDescriptors函数进行配置描述符校验,如果失败,则设备启动失败。
- USBVideoPnpStart
- StartUSBVideoDevice
- DumpAndValidateAllDescriptors
- StartUSBVideoDevice
DumpAndValidateAllDescriptors函数的反汇编如下:
_int64 __fastcall DumpAndValidateAllDescriptors(USB_CONFIGURATION_DESCRIPTOR *pDes)
{
int iConfiguration; // esi
unsigned int status; // ecx
char *pEnd; // rdi
char *v6; // rdx
int ntStatus; // eax
unsigned __int16 v8; // [rsp+30h] [rbp+8h] BYREF
iConfiguration = 0;
v8 = 0;
status = 0;
if ( pDes->bDescriptorType != 2 ) // USB_CONFIGURATION_DESCRIPTOR_TYPE
return 0xC0000001i64; // STATUS_UNSUCCESSFUL
pEnd = (char *)(&pDes->bLength + pDes->wTotalLength);
while ( pDes < (USB_CONFIGURATION_DESCRIPTOR *)pEnd )
{
if ( &pDes->wTotalLength > (USHORT *)pEnd || (char *)pDes + pDes->bLength > pEnd )
return 0xC0000001;
switch ( pDes->bDescriptorType )
{
case 2u: // USB_CONFIGURATION_DESCRIPTOR_TYPE
ntStatus = DumpAndValidateConfigDescriptor(pDes, pEnd);
break;
case 4u: // USB_INTERFACE_ASSOCIATION_DESCRIPTOR
ntStatus = DumpAndValidateInterfaceDescriptor(pDes, pEnd);
iConfiguration = pDes->iConfiguration;
break;
case 5u: // USB_ENDPOINT_DESCRIPTOR
ntStatus = DumpAndValidateEndpointDescriptor(pDes, pEnd);
break;
case 0x24u: // USB_VIDEO_DESCRIPTOR
ntStatus = DumpAndValidateClassSpecInterfaceDescriptor(
(USB_INTERFACE_DESCRIPTOR *)pDes,
pEnd,
iConfiguration,
&v8);
break;
case 0x25u: // VC_INTERRUPT_ENDPOINT_DESCRIPTOR
v6 = pEnd;
if ( LOBYTE(pDes->wTotalLength) != 3 )
goto LABEL_12;
ntStatus = DumpAndValidateClassSpecificInterruptEndpointDescriptor(pDes, pEnd);
break;
default:
v6 = pEnd;
LABEL_12:
ntStatus = DumpAndValidateGenericDescriptor(pDes, v6);
break;
}
status = ntStatus;
if ( ntStatus < 0 )
return status;
pDes = (USB_CONFIGURATION_DESCRIPTOR *)((char *)pDes + pDes->bLength);
}
return status;
}
在UVC中,重点关键描述符类型为0x24的.
在DumpAndValidateClassSpecInterfaceDescriptor函数中调用DumpAndValidateInputHeader来校验UVC输入头描述符。
其关键代码如下:
对于结构体定义:
typedef struct _USB_VS_INTERFACE_INPUT_HEADER_DESCRIPTOR
{
UINT8 bLength;
UINT8 bDescriptorType;
UINT8 bDescriptorSubtype;
UINT8 bNumFormats;
UINT16 wTotalLength;
UINT8 bEndpointAddress;
UINT8 bmInfo;
UINT8 bTerminalLink;
UINT8 bStillCaptureMethod;
UINT8 bTriggerSupport;
UINT8 bTriggerUsage;
UINT8 bControlSize;
UINT8 bmaControls[bControlSize];
} USB_VS_INTERFACE_INPUT_HEADER_DESCRIPTOR;
bControlSize字段包括bControlSize本身总其13个字节,bmaControls的数量因bNumFormats变化而变化。
注意上面的是等号判断,而不是>=,必须bmaControls不像有的描述符后面中可以有空字节,其长度必须严格一致。
本人在HOOK配置描述符时,因定义了bmaControls数组为2,导致实际配置描述符长度和bNumFormats的不一致,会引起设备启动失改,返回的错误号也刚好是STATUS_UNSUCCESSFUL,故不会再有选择配置请求。
关于这个问题,本人昨天查了一天,今天通过反汇编才定位到此。
因为本人熟知获取配置描述符之后,会有选择配置请求,但是因上面的问题,导致没有选择配置请求。故问题一定是因配置描述符引起的,但配置描述符有400多个字节,又因为本人因以前的相关描述符定义不是很规范,但是Windows可以兼容,导致问题一直迟迟未定位。
题外话:
因windows未开源,在调试过程中可能会遇到奇奇怪怪的问题人,但是问题很难定位。但有强大的IDA,可以通过反汇编和通过Windbg栈回溯,可以定位到上层代码段中,这样就可以再静态分析。如这样:https://www.usbzh.com/article/detail-1405.html