#define LOG_MODULE "cam-hook" // clang-format off // Don't format because the order is important here #include #include #include #include // clang-format on #include #include #include #include #include #include #include "hook/com-proxy.h" #include "hook/table.h" #include "camhook/cam.h" #include "camhook/cam-detect.h" #include "util/defs.h" #include "util/log.h" #include "util/str.h" #define CAMHOOK_NUM_LAYOUTS 3 static struct camera_data camData[CAMHOOK_CONFIG_CAM_MAX]; int camAddresses[CAMHOOK_NUM_LAYOUTS][CAMHOOK_CONFIG_CAM_MAX] = { {1, 7}, {4, 9}, {4, 3}, }; static size_t num_addressed_cams = 0; static size_t num_located_cams = 0; static int camhook_port_layout = 0; static enum camhook_version camhook_version = CAMHOOK_VERSION_OLD; static CONFIGRET my_CM_Locate_DevNodeA( PDEVINST pdnDevInst, DEVINSTID_A pDeviceID, ULONG ulFlags); static CONFIGRET my_CM_Get_Parent(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); static CONFIGRET my_CM_Get_Device_IDA( DEVINST dnDevInst, PSTR Buffer, ULONG BufferLen, ULONG ulFlags); static CONFIGRET (*real_CM_Locate_DevNodeA)( PDEVINST pdnDevInst, DEVINSTID_A pDeviceID, ULONG ulFlags); static CONFIGRET (*real_CM_Get_Parent)( PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); static CONFIGRET (*real_CM_Get_Device_IDA)( DEVINST dnDevInst, PSTR Buffer, ULONG BufferLen, ULONG ulFlags); static HRESULT my_MFEnumDeviceSources( IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate, UINT32 *pcSourceActivate); static HRESULT (*real_MFEnumDeviceSources)( IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate, UINT32 *pcSourceActivate); static BOOL my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet); static BOOL (*real_SetupDiDestroyDeviceInfoList)(HDEVINFO DeviceInfoSet); static BOOL my_SetupDiEnumDeviceInfo( HDEVINFO DeviceInfoSet, DWORD MemberIndex, PSP_DEVINFO_DATA DeviceInfoData); static BOOL (*real_SetupDiEnumDeviceInfo)( HDEVINFO DeviceInfoSet, DWORD MemberIndex, PSP_DEVINFO_DATA DeviceInfoData); static BOOL my_SetupDiGetDeviceRegistryPropertyA( HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize); static BOOL (*real_SetupDiGetDeviceRegistryPropertyA)( HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize); static HDEVINFO my_SetupDiGetClassDevsA( CONST GUID *ClassGuid, PCSTR Enumerator, HWND hwndParent, DWORD Flags); static HDEVINFO (*real_SetupDiGetClassDevsA)( CONST GUID *ClassGuid, PCSTR Enumerator, HWND hwndParent, DWORD Flags); static BOOL STDCALL my_DeviceIoControl( HANDLE hFile, uint32_t dwIoControlCode, void *lpInBuffer, uint32_t nInBufferSize, void *lpOutBuffer, uint32_t nOutBufferSize, uint32_t *lpBytesReturned, OVERLAPPED *lpOverlapped); static BOOL(STDCALL *real_DeviceIoControl)( HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, void *out_bytes, uint32_t out_nbytes, uint32_t *out_returned, OVERLAPPED *ovl); static const struct hook_symbol camhook_cfgmgr32_syms[] = { {.name = "CM_Locate_DevNodeA", .patch = my_CM_Locate_DevNodeA, .link = (void **) &real_CM_Locate_DevNodeA}, {.name = "CM_Get_Parent", .patch = my_CM_Get_Parent, .link = (void **) &real_CM_Get_Parent}, {.name = "CM_Get_Device_IDA", .patch = my_CM_Get_Device_IDA, .link = (void **) &real_CM_Get_Device_IDA}, {.name = "SetupDiDestroyDeviceInfoList", .patch = my_SetupDiDestroyDeviceInfoList, .link = (void **) &real_SetupDiDestroyDeviceInfoList}, {.name = "SetupDiEnumDeviceInfo", .patch = my_SetupDiEnumDeviceInfo, .link = (void **) &real_SetupDiEnumDeviceInfo}, {.name = "SetupDiGetDeviceRegistryPropertyA", .patch = my_SetupDiGetDeviceRegistryPropertyA, .link = (void **) &real_SetupDiGetDeviceRegistryPropertyA}, {.name = "SetupDiGetClassDevsA", .patch = my_SetupDiGetClassDevsA, .link = (void **) &real_SetupDiGetClassDevsA}, }; static const struct hook_symbol camhook_cfgmgr32_syms_new[] = { {.name = "CM_Locate_DevNodeA", .patch = my_CM_Locate_DevNodeA, .link = (void **) &real_CM_Locate_DevNodeA}, }; static const struct hook_symbol camhook_mf_syms[] = { {.name = "MFEnumDeviceSources", .patch = my_MFEnumDeviceSources, .link = (void **) &real_MFEnumDeviceSources}, }; static struct hook_symbol camhook_ioctl_syms[] = { {.name = "DeviceIoControl", .patch = my_DeviceIoControl, .link = (void *) &real_DeviceIoControl}, }; DEVINST camhook_custom_nodes[CAMHOOK_CONFIG_CAM_MAX] = { 0x04040004, 0x04040008, }; DEVINST camhook_custom_parent_nodes[CAMHOOK_CONFIG_CAM_MAX] = { 0x04040014, 0x04040018, }; const char *camhook_custom_parent_device_id[CAMHOOK_CONFIG_CAM_MAX] = { "USB\\VEN_1022&DEV_7908", "USB\\VEN_1022&DEV_7914", }; static CONFIGRET my_CM_Locate_DevNodeA(PDEVINST pdnDevInst, DEVINSTID_A pDeviceID, ULONG ulFlags) { log_info("Inside: %s", __FUNCTION__); char builtString[CAMERA_DATA_STRING_SIZE] = {0}; if (pdnDevInst) { log_info("seeking: %s", pDeviceID); for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) { if (camData[i].setup) { if (camhook_version == CAMHOOK_VERSION_OLD) { snprintf( builtString, CAMERA_DATA_STRING_SIZE, "USB\\VID_288C&PID_0002&MI_00\\%s", camData[i].extra_upper); } else if (camhook_version == CAMHOOK_VERSION_NEW) { snprintf( builtString, CAMERA_DATA_STRING_SIZE, "USB\\VID_05A3&PID_9230&MI_00\\%s", camData[i].extra_upper); } log_info("built: %s", builtString); if (strcmp(pDeviceID, builtString) == 0) { if (camhook_version == CAMHOOK_VERSION_OLD) { if (!camData[i].fake_located) { camData[i].fake_located_node = num_located_cams; camData[i].fake_located = true; ++num_located_cams; } log_info( "Injecting custom device %d to node %x", (int) i, (int) camData[i].fake_located_node); *pdnDevInst = camhook_custom_nodes[camData[i].fake_located_node]; return CR_SUCCESS; } else if (camhook_version == CAMHOOK_VERSION_NEW) { // inject original device log_info( "Injecting original device %d %s -> %s", (int) i, pDeviceID, camData[i].deviceInstancePath); pDeviceID = camData[i].deviceInstancePath; } } } } } return real_CM_Locate_DevNodeA(pdnDevInst, pDeviceID, ulFlags); } static CONFIGRET my_CM_Get_Parent(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags) { log_info("Inside: %s", __FUNCTION__); if (pdnDevInst) { for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) { if (dnDevInst == camhook_custom_nodes[i]) { log_info("Injecting custom parent %d", (int) i); *pdnDevInst = camhook_custom_parent_nodes[i]; return CR_SUCCESS; } } } return real_CM_Get_Parent(pdnDevInst, dnDevInst, ulFlags); } static CONFIGRET my_CM_Get_Device_IDA( DEVINST dnDevInst, PSTR Buffer, ULONG BufferLen, ULONG ulFlags) { log_info("Inside: %s", __FUNCTION__); if (Buffer) { for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) { if (dnDevInst == camhook_custom_parent_nodes[i]) { log_info("Injecting custom parent %d ID", (int) i); strncpy(Buffer, camhook_custom_parent_device_id[i], BufferLen); Buffer[BufferLen - 1] = '\0'; log_info("%s", Buffer); return CR_SUCCESS; } } } return real_CM_Get_Device_IDA(dnDevInst, Buffer, BufferLen, ulFlags); } static HRESULT(STDCALL *real_GetAllocatedString)( IMFActivate *self, REFGUID guidKey, LPWSTR *ppwszValue, UINT32 *pcchLength); HRESULT my_GetAllocatedString( IMFActivate *self, REFGUID guidKey, LPWSTR *ppwszValue, UINT32 *pcchLength) { HRESULT ret; // log_info("Inside: %s", __FUNCTION__); // should probably check GUID == MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, oh well ret = real_GetAllocatedString(self, guidKey, ppwszValue, pcchLength); char *pMBBuffer = (char *) malloc(0x100); wcstombs(pMBBuffer, *ppwszValue, 0x100); log_info("Obtained: %s", pMBBuffer); wchar_t *pwc = NULL; // look for a matching deviceSymbolicLink for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) { if (!pwc) { if (camData[i].setup) { pwc = wcsstr(*ppwszValue, camData[i].deviceSymbolicLink); } } } // if matches, replace with target device ID if (pwc) { if (camhook_version == CAMHOOK_VERSION_OLD) { // \\?\usb#vid_288c&pid_0002&mi_00 pwc[12] = L'2'; pwc[13] = L'8'; pwc[14] = L'8'; pwc[15] = L'c'; pwc[21] = L'0'; pwc[22] = L'0'; pwc[23] = L'0'; pwc[24] = L'2'; pwc[29] = L'0'; pwc[30] = L'0'; } else if (camhook_version == CAMHOOK_VERSION_NEW) { // \\?\usb#vid_05a3&pid_9230&mi_00 pwc[12] = L'0'; pwc[13] = L'5'; pwc[14] = L'a'; pwc[15] = L'3'; pwc[21] = L'9'; pwc[22] = L'2'; pwc[23] = L'3'; pwc[24] = L'0'; pwc[29] = L'0'; pwc[30] = L'0'; } wcstombs(pMBBuffer, *ppwszValue, 0x100); log_info("Replaced: %s", pMBBuffer); } free(pMBBuffer); return ret; } static HRESULT my_MFEnumDeviceSources( IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate, UINT32 *pcSourceActivate) { IMFActivate *api; IMFActivateVtbl *api_vtbl; struct com_proxy *api_proxy; UINT32 nsrcs; HRESULT ret; // log_info("Inside: %s", __FUNCTION__); ret = real_MFEnumDeviceSources( pAttributes, pppSourceActivate, pcSourceActivate); nsrcs = *pcSourceActivate; for (UINT32 i = 0; i < nsrcs; ++i) { api = (*pppSourceActivate)[i]; ret = com_proxy_wrap(&api_proxy, api, sizeof(*api->lpVtbl)); if (ret != S_OK) { log_warning("Wrapping com proxy failed: %08lx", ret); return ret; } api_vtbl = api_proxy->vptr; real_GetAllocatedString = api_vtbl->GetAllocatedString; api_vtbl->GetAllocatedString = my_GetAllocatedString; (*pppSourceActivate)[i] = (IMFActivate *) api_proxy; } return ret; } #define CUSTOM_DEVICE_HANDLE (void *) 0x04041110 static HDEVINFO RealReplacedHandle; static BOOL my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet) { if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE) { log_info("Inside: %s", __FUNCTION__); DeviceInfoSet = RealReplacedHandle; RealReplacedHandle = NULL; return real_SetupDiDestroyDeviceInfoList(DeviceInfoSet); } return real_SetupDiDestroyDeviceInfoList(DeviceInfoSet); } static BOOL my_SetupDiEnumDeviceInfo( HDEVINFO DeviceInfoSet, DWORD MemberIndex, PSP_DEVINFO_DATA DeviceInfoData) { if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE) { // log_info("Inside: %s", __FUNCTION__); return real_SetupDiEnumDeviceInfo( RealReplacedHandle, MemberIndex, DeviceInfoData); } return real_SetupDiEnumDeviceInfo( DeviceInfoSet, MemberIndex, DeviceInfoData); } static BOOL my_SetupDiGetDeviceRegistryPropertyA( HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize) { if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE) { BOOL ret = real_SetupDiGetDeviceRegistryPropertyA( RealReplacedHandle, DeviceInfoData, Property, PropertyRegDataType, PropertyBuffer, PropertyBufferSize, RequiredSize); if (Property == SPDRP_DEVICEDESC) { if (PropertyBuffer) { char *PropertyBufferChar = (char *) PropertyBuffer; for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) { if (camData[i].setup) { if (strcmp( PropertyBufferChar, camData[i].parent_name) == 0) { log_info( "%s: replacing %s", __FUNCTION__, camData[i].parent_name); strncpy( PropertyBufferChar, "USB Composite Device", PropertyBufferSize); } } } } return ret; } else if (Property == SPDRP_ADDRESS) { if (PropertyBuffer) { int addr = *(int *) PropertyBuffer; for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) { if (camData[i].setup) { if (addr == camData[i].parent_address) { if (!camData[i].fake_addressed) { // old style always uses set 0 camData[i].fake_address = camAddresses[0][num_addressed_cams]; camData[i].fake_addressed = true; ++num_addressed_cams; } log_info( "%s: assigning cam %d to addr %d", __FUNCTION__, (int) i, camData[i].fake_address); *(int *) PropertyBuffer = camData[i].fake_address; } } } } return ret; } } return real_SetupDiGetDeviceRegistryPropertyA( DeviceInfoSet, DeviceInfoData, Property, PropertyRegDataType, PropertyBuffer, PropertyBufferSize, RequiredSize); } static HDEVINFO my_SetupDiGetClassDevsA( CONST GUID *ClassGuid, PCSTR Enumerator, HWND hwndParent, DWORD Flags) { if (ClassGuid == NULL && Enumerator != NULL && hwndParent == NULL && Flags == (DIGCF_PRESENT | DIGCF_ALLCLASSES)) { if (RealReplacedHandle) { log_info("Replacement handle is already set?"); } if (strcmp(Enumerator, "USB") == 0) { log_info("Inside: %s", __FUNCTION__); RealReplacedHandle = real_SetupDiGetClassDevsA( ClassGuid, Enumerator, hwndParent, Flags); if (RealReplacedHandle == INVALID_HANDLE_VALUE) { return INVALID_HANDLE_VALUE; } return CUSTOM_DEVICE_HANDLE; } } return real_SetupDiGetClassDevsA(ClassGuid, Enumerator, hwndParent, Flags); } ULONG get_matching_device_replacement_id( HANDLE hFile, ULONG ConnectionIndex, USB_DEVICE_DESCRIPTOR* desc) { ULONG replacement_id = 0; for (size_t i = 0; i < CAMHOOK_CONFIG_CAM_MAX; ++i) { if (camData[i].setup) { // log_info("Checking %lu %04x %04x vs %lu %04x %04x", // ConnectionIndex, desc->idVendor, desc->idProduct, // camData[i].address, camData[i].vid, camData[i].pid); if ( ConnectionIndex == camData[i].parent_address && desc->idVendor == camData[i].vid && desc->idProduct == camData[i].pid ) { // do secondary check for driver key using // IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME USB_NODE_CONNECTION_DRIVERKEY_NAME req; req.ActualLength = 0; req.ConnectionIndex = ConnectionIndex; req.DriverKeyName[0] = '\0'; DWORD nBytes; if (!DeviceIoControl( hFile, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, &req, sizeof(req), &req, sizeof(req), &nBytes, 0LL )) { log_warning( "Failed to get driver key name size for device %04x %04x", desc->idVendor, desc->idProduct); continue; } if (req.ActualLength <= sizeof(req)) { log_warning( "Driver key name size too small for device %04x %04x", desc->idVendor, desc->idProduct); continue; } nBytes = req.ActualLength; USB_NODE_CONNECTION_DRIVERKEY_NAME *driverKeyNameW = malloc(nBytes); if (!driverKeyNameW) { log_warning("Failed to allocate driver key name buffer for device %04x %04x", desc->idVendor, desc->idProduct); continue; } driverKeyNameW->ActualLength = 0; driverKeyNameW->ConnectionIndex = ConnectionIndex; driverKeyNameW->DriverKeyName[0] = '\0'; if (!DeviceIoControl( hFile, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, driverKeyNameW, nBytes, driverKeyNameW, nBytes, &nBytes, 0LL )) { log_warning( "Failed to get driver key name for device %04x %04x", desc->idVendor, desc->idProduct); continue; } char *driverKeyNameA = NULL; wstr_narrow(driverKeyNameW->DriverKeyName, &driverKeyNameA); free(driverKeyNameW); if (strcmp(driverKeyNameA, camData[i].parent_driverKey) != 0) { // log just in case? log_info( "Driver key name mismatch for device %04x %04x %s != %s", desc->idVendor, desc->idProduct, driverKeyNameA, camData[i].parent_driverKey); if (driverKeyNameA) { free(driverKeyNameA); } continue; } log_info( "Replacing device @ %lu with %d for %04x %04x %s", ConnectionIndex, camAddresses[camhook_port_layout][i], desc->idVendor, desc->idProduct, driverKeyNameA); if (driverKeyNameA) { free(driverKeyNameA); } replacement_id = camAddresses[camhook_port_layout][i]; break; } } } return replacement_id; } static BOOL STDCALL my_DeviceIoControl( HANDLE hFile, uint32_t dwIoControlCode, void *lpInBuffer, uint32_t nInBufferSize, void *lpOutBuffer, uint32_t nOutBufferSize, uint32_t *lpBytesReturned, OVERLAPPED *lpOverlapped) { BOOL res; res = real_DeviceIoControl( hFile, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped); // if error just return if (!res) { return res; } // detect IOCTL_USB_GET_NODE_CONNECTION_INFORMATION (_EX) // we don't bother faking the rest as it's never read if (dwIoControlCode == IOCTL_USB_GET_NODE_CONNECTION_INFORMATION) { USB_NODE_CONNECTION_INFORMATION *connectionInfo = lpOutBuffer; ULONG replacement_id = get_matching_device_replacement_id( hFile, connectionInfo->ConnectionIndex, &connectionInfo->DeviceDescriptor); if (replacement_id > 0) { connectionInfo->ConnectionIndex = replacement_id; } } else if (dwIoControlCode == IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX) { USB_NODE_CONNECTION_INFORMATION_EX *connectionInfoEx = lpOutBuffer; ULONG replacement_id = get_matching_device_replacement_id( hFile, connectionInfoEx->ConnectionIndex, &connectionInfoEx->DeviceDescriptor); if (replacement_id > 0) { connectionInfoEx->ConnectionIndex = replacement_id; } } return res; } void camhook_set_version(enum camhook_version version) { camhook_version = version; } void camhook_init(struct camhook_config_cam *config_cam) { MFStartup(0x20070u, 0); // fill before applying hooks for (size_t i = 0; i < config_cam->num_devices; ++i) { // Check if this camera is disabled first if (config_cam->disable_camera[i]) { // If so, pretend this camera is already assigned and move on num_addressed_cams++; num_located_cams++; continue; } // If not, try to hook the camera fill_cam_struct(&camData[i], config_cam->device_id[i]); } size_t num_setup = 0; for (size_t i = 0; i < config_cam->num_devices; ++i) { if (camData[i].setup) { num_setup++; } } camhook_port_layout = config_cam->port_layout; if (camhook_port_layout < 0 || camhook_port_layout >= CAMHOOK_NUM_LAYOUTS) { camhook_port_layout = 0; } if (num_setup > 0) { // always hook_table_apply( NULL, "Mf.dll", camhook_mf_syms, lengthof(camhook_mf_syms)); if (camhook_version == CAMHOOK_VERSION_OLD) { hook_table_apply( NULL, "cfgmgr32.dll", camhook_cfgmgr32_syms, lengthof(camhook_cfgmgr32_syms)); } else if (camhook_version == CAMHOOK_VERSION_NEW) { // for CAMHOOK_VERSION_NEW we restore original VID/PID // so the parent lookup succeeds later // yes the DLL moved??? hook_table_apply( NULL, "setupapi.dll", camhook_cfgmgr32_syms_new, lengthof(camhook_cfgmgr32_syms_new)); // they copied / forked usb/usbview/enum.c // however they don't use most of it // so we only need DeviceIoControl to inject port # hook_table_apply( NULL, "kernel32.dll", camhook_ioctl_syms, lengthof(camhook_ioctl_syms)); } log_info("Inserted cam hooks for %d cams", (int) num_setup); // If the user has manually disabled all cams, don't print this in the // log } else if (num_addressed_cams != config_cam->num_devices) { log_info("No cams detected, not hooking"); } } void camhook_fini(void) { MFShutdown(); }