IP-KVM Hardware Hiding Behind Spoofed USB Descriptors
WindowsmacOS
TTP Hunt high DPRK IT Worker FraudLaptop Farm
Catches an IP-KVM whose vendor, product and serial strings have been renamed to something innocuous, using the USB configuration descriptor and interface shape the device cannot change without ceasing to work.
What this hunt looks for
The descriptors in the preceding hunt are strings the device chooses to report, and a PiKVM's are editable. Grumpy Goose Labs found that the USB configuration descriptor survives a stock rename, reading "Config 1: PiKVM device" on a PiKVM and "Config 1: ECM device" on a TinyPilot, and joins the connection event through the descriptor-set hash.
The same research reads the interface count and the maximum power draw beside it, and chelmzy.tech hunts on a base class presented with audio and mass-storage interfaces rather than on any name. Both devices also default to a Linux monitor identifier on the HDMI connection, which is itself an artefact.
TinyPilot's knowledge base tells Windows users to rename its sound device to Toshiba-H2C in mmsys.cpl. That per-endpoint registry override leaves both the suggested string and the untouched USB device instance path underneath it for a sweep to find.
- Technique
- Hardware addition (MITRE ATT&CK T1200) with masquerading of the device identity (T1036) to defeat name-based USB allowlisting and detection.
- Data sources
- EDR USB descriptor and device-class telemetry, Windows registry (USB enumeration), Windows registry (audio endpoints), EDR registry telemetry, WMI monitor identity, osquery
- Last updated
- 2026-10-04
Indicators
The atomic values this hunt matches on, listed separately from the queries so they can be lifted into tooling that speaks none of the query languages below. Every value here is reported by one of the sources cited at the foot of this page.
| Type | Value | Note |
|---|---|---|
| USB configuration name | Config 1: PiKVM device |
ConfigurationDescriptorName reported by a PiKVM, surviving a rename of the vendor and product strings (Grumpy Goose Labs). |
| USB configuration name | Config 1: ECM device |
ConfigurationDescriptorName reported by a TinyPilot (Grumpy Goose Labs). |
| Regex | /Config [0-9]: */i |
The published match, written loosely because the configuration number has been observed changing (Grumpy Goose Labs). |
| Correlation field | DeviceDescriptorSetHash |
Hash over vendor id, product id and serial that joins the descriptor event to the connection event. Shared across devices with identical configuration, so it identifies a configuration rather than a physical device (Grumpy Goose Labs). |
| Descriptor field | ConfigurationDescriptorNumInterfaces |
Interface count. A device whose claimed identity does not account for five interfaces is worth pulling (Grumpy Goose Labs). |
| USB class combination | 00h + 01h + 08h |
Base class presented together with audio and mass-storage interfaces on one composite device (chelmzy.tech). |
| USB class combination | 00h + 08h |
Base class with a mass-storage interface, particularly where the reported device name has no mass-storage function (chelmzy.tech). |
| Audio device name | Toshiba-H2C |
The name TinyPilot's own documentation instructs Windows users to give the TinyPilot audio endpoint in mmsys.cpl so that it stops appearing under the product name. The string is arbitrary and an operator is free to pick another; what makes this one worth matching is that the instruction is public, concrete and widely read, so the same value recurs across unrelated machines. |
| Registry path | HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio |
Where the rename lands. Under each Render and Capture endpoint, the Properties key holds the endpoint name in {a45c254e-df1c-4efd-8020-67d146a850e0},2, which mmsys.cpl edits, alongside the adapter description in {b3f8fa53-0004-438e-9003-51a46e139bfc},6 and the device instance path in {b3f8fa53-0004-438e-9003-51a46e139bfc},2, which it does not. |
| USB serial | 6b65796d696d657069 |
Fixed serial every TinyPilot presents, written by /opt/tinypilot-privileged/init-usb-gadget and documented by the vendor; the hex decodes to "keymimepi". It appears in the device instance path of the audio endpoint the rename is meant to disguise, which is why the sweep below reads that path rather than the displayed name. |
Logic
Every query below implements these steps, so the same hunt returns the same result in each product.
- Collect: USB configuration descriptors and device enrolment (Windows, macOS), plus Windows audio-endpoint registry writes
- Match: configuration name is 'Config N: PiKVM device' or 'Config N: ECM device', or the default serial CAFEBABE or 6b65796d696d657069
- Match: or an audio endpoint value under MMDevices\Audio set to Toshiba-H2C, TinyPilot or PiKVM, or carrying the TinyPilot serial
- Match: or (optional) gadget id VID_1D6B&PID_0104, 5 or more interfaces, or 3 or more PnP setup classes on one device
- Report: hosts and devices with a match, newest first
Queries
Defender / Sentinel KQL
Written for this site, from indicators reported by Grumpy Goose Labs, TinyPilotAny row names a host whose USB or audio-endpoint artifacts survive a renamed IP-KVM; read the Evidence column for the matched value.
// Hunt: IP-KVM with renamed USB strings. Default serials survive a rename; so does the audio-endpoint
// rename TinyPilot documents (Toshiba-H2C).
union
// optional: one hardware id enrolling under 3 or more setup classes, which approximates a composite KVM; webcams, headsets and docks match too
( DeviceEvents
| where Timestamp > ago(90d) and ActionType == "PnpDeviceConnected"
| extend Fields = parse_json(AdditionalFields)
| extend HardwareId = extract(@"(VID_[0-9A-Fa-f]{4}&PID_[0-9A-Fa-f]{4})", 1, tostring(Fields.DeviceId))
| where isnotempty(HardwareId)
| summarize Timestamp = max(Timestamp), Classes = make_set(tostring(Fields.ClassName)) by DeviceName, HardwareId
| where array_length(Classes) >= 3
| project Timestamp, DeviceName, Signal = "three-setup-classes", Evidence = strcat(HardwareId, " ", tostring(Classes)) ),
( DeviceEvents
| where Timestamp > ago(90d) and ActionType == "PnpDeviceConnected"
| extend Raw = tostring(AdditionalFields)
| where Raw has_any ("cafebabe", "6b65796d696d657069")
// optional: generic Linux USB gadget id, shared by any board emulating peripherals
or Raw contains "VID_1D6B&PID_0104"
| project Timestamp, DeviceName, Signal = "default-serial-or-gadget-id", Evidence = Raw ),
( DeviceRegistryEvents
| where Timestamp > ago(90d) and ActionType in ("RegistryValueSet", "RegistryKeyCreated")
| where RegistryKey contains @"CurrentVersion\MMDevices\Audio"
| where RegistryValueData contains "Toshiba-H2C" or RegistryValueData has_any ("tinypilot", "pikvm", "6b65796d696d657069")
| project Timestamp, DeviceName, Signal = "audio-endpoint-rename", Evidence = strcat(RegistryValueName, " = ", RegistryValueData) )
| summarize FirstSeen = min(Timestamp), LastSeen = max(Timestamp), Signals = make_set(Signal), Evidence = make_set(Evidence, 10) by DeviceName
| sort by LastSeen desc
Lower-fidelity parts (2)
- the three-setup-classes branch — webcams with microphones, headsets and docks also enrol under several setup classes. Remove: delete from the // optional: one hardware id line through the closing `),` of that branch
- VID_1D6B&PID_0104 — generic Linux gadget id shared by any board emulating USB peripherals. Remove: delete the // optional: generic Linux USB gadget id comment and the `or Raw contains` line under it
Sigma
Written for this site, from indicators reported by TinyPilot, Grumpy Goose LabsMatches the audio-endpoint rename TinyPilot documents, using the registry_set category so it runs on any Sysmon- or EDR-fed backend.
title: USB Audio Endpoint Renamed to Hide an IP-KVM
id: 4c0b53a6-7e0d-4f3e-9a5b-1d2f6a8e3c71
status: experimental
description: >
Detects a write under the Windows MMDevices audio endpoint keys that names the
endpoint after TinyPilot, PiKVM or the string TinyPilot's own documentation
tells Windows users to enter in mmsys.cpl (Toshiba-H2C), or that carries the
fixed TinyPilot USB serial in the endpoint's device path. The rename hides the
product name; the serial and the suggested string remain.
references:
- https://docs.tinypilotkvm.com/article/22-can-anyone-detect-when-im-using-tinypilot
- https://blog.grumpygoose.io/unemployfuscation-1a1721485312
logsource:
category: registry_set
detection:
selection_key:
TargetObject|contains: 'CurrentVersion\MMDevices\Audio'
selection_value:
Details|contains:
- 'Toshiba-H2C'
- 'TinyPilot'
- 'PiKVM'
- '6b65796d696d657069'
condition: selection_key and selection_value
falsepositives:
- A person who legitimately names an audio endpoint after a TinyPilot or PiKVM they own
level: high
Splunk SPL
Written for this site, from indicators reported by Grumpy Goose Labs, TinyPilotsignals shows which of the audio-endpoint rename, default serial or gadget id fired for each host.
```Placeholders: index=edr is your EDR registry and process events; index=wineventlog is WinEventLog:Security (event 6416).```
((index=edr "MMDevices" ("Toshiba-H2C" OR "TinyPilot" OR "PiKVM" OR "6b65796d696d657069"))
OR (index=wineventlog EventCode=6416 ("CAFEBABE" OR "6b65796d696d657069"))
```optional: generic Linux USB gadget id, shared by any board emulating peripherals```
OR (index=wineventlog EventCode=6416 "VID_1D6B&PID_0104")
)
| eval signal=case(index="edr", "audio-endpoint-rename", match(_raw, "(?i)cafebabe|6b65796d696d657069"), "default-serial", true(), "gadget-id")
| stats min(_time) AS first_seen max(_time) AS last_seen values(signal) AS signals BY host
| sort - last_seen
| convert ctime(first_seen) ctime(last_seen)
Lower-fidelity parts (1)
- the VID_1D6B&PID_0104 branch — generic Linux gadget id shared by any board emulating USB peripherals. Remove: delete the ```optional comment and the OR (index=wineventlog ...) line under it; the case() stays valid
CrowdStrike LogScale
Written for this site, from indicators reported by Grumpy Goose LabsThe configuration name survives a stock rename of the vendor, product and serial strings, so a config-name hit is the strongest result here.
// Hunt: IP-KVM with renamed USB strings. Field names are those of the Grumpy Goose Labs Falcon query.
(#event_simpleName=DcUsbConfigurationDescriptor or #event_simpleName=DcUsbDeviceConnected)
| case {
#event_simpleName=DcUsbConfigurationDescriptor ConfigurationDescriptorName=/Config [0-9]: (PiKVM|ECM) device/i | signal := "config-name";
#event_simpleName=DcUsbDeviceConnected DeviceSerialNumber=/cafebabe|6b65796d696d657069/i | signal := "default-serial";
// optional: five interfaces that the claimed identity does not account for; headsets and docks can reach it
#event_simpleName=DcUsbConfigurationDescriptor ConfigurationDescriptorNumInterfaces >= 5 | signal := "five-interfaces";
* | signal := "";
}
| signal != ""
| groupBy([aid, ComputerName, DeviceDescriptorSetHash], function=[collect([signal, ConfigurationDescriptorName, DeviceManufacturer, DeviceProduct, DeviceSerialNumber]), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen)])
| sort(lastSeen, order=desc)
Lower-fidelity parts (1)
- the five-interfaces branch — composite headsets and docks can present five interfaces. Remove: delete the // optional: five interfaces comment and the line under it
Elastic
Written for this site, from indicators reported by Grumpy Goose Labs, TinyPilotReads Elastic Defend registry events and the Windows 6416 event; signals names the artifact that fired.
FROM logs-endpoint.events.registry-*, logs-system.security-*, winlogbeat-*
| WHERE @timestamp > NOW() - 90 days
| EVAL reg = TO_LOWER(CONCAT(COALESCE(registry.path, ""), " ", COALESCE(MV_CONCAT(registry.data.strings, " "), "")))
| EVAL usb = TO_LOWER(COALESCE(winlog.event_data.DeviceId, ""))
| EVAL signal = CASE(
reg RLIKE ".*mmdevices.audio.*(toshiba-h2c|tinypilot|pikvm|6b65796d696d657069).*", "audio-endpoint-rename",
event.code == "6416" AND usb RLIKE ".*(cafebabe|6b65796d696d657069).*", "default-serial",
"")
| EVAL signal = CASE(signal != "", signal, event.code == "6416" AND usb RLIKE ".*vid_1d6b&pid_0104.*", "gadget-id", "")
| WHERE signal != ""
| STATS first_seen = MIN(@timestamp), last_seen = MAX(@timestamp), signals = VALUES(signal) BY host.name
| SORT last_seen DESC
Lower-fidelity parts (1)
- the gadget-id EVAL line — generic Linux gadget id shared by any board emulating USB peripherals. Remove: delete the second `| EVAL signal = CASE(signal != ""` line
Cortex XQL
Written for this site, from indicators reported by TinyPilotFinds the audio-endpoint rename in xdr_data registry events; USB descriptor and serial checks need the Device Control data.
// Hunt: IP-KVM with renamed USB strings. xdr_data carries the audio-endpoint rename; USB descriptors are not in it.
dataset = xdr_data
| filter event_type = ENUM.REGISTRY and action_registry_key_name ~= "(?i)CurrentVersion.MMDevices.Audio"
and action_registry_data ~= "(?i)toshiba-h2c|tinypilot|pikvm|6b65796d696d657069"
| comp min(_time) as first_seen, max(_time) as last_seen, values(action_registry_data) as values_set by agent_hostname
| sort desc last_seen
osquery
Written for this site, from indicators reported by Grumpy Goose LabsLocal check on macOS and Linux for a device that kept its default serial after its name strings were changed.
-- Hunt: IP-KVM with renamed USB strings. The default serial survives a rename of the name strings.
-- usb_devices exists on macOS and Linux only; Windows hosts are covered by the registry and event queries.
SELECT serial, vendor_id, model_id, vendor, model
FROM usb_devices
WHERE serial IN ('CAFEBABE', '6b65796d696d657069')
OR (vendor_id = '1d6b' AND model_id = '0104') -- optional: generic Linux gadget id, shared by any board emulating peripherals
Lower-fidelity parts (1)
- the gadget-id condition — generic Linux gadget id shared by any board emulating USB peripherals. Remove: delete the line ending in -- optional
False positives and tuning
Grumpy Goose Labs rates the configuration-name detection as typically high fidelity, but a match shows only that a Raspberry Pi based HID device is attached. The WmiMonitorID check is weaker, being obfuscatable.
Composite hardware such as webcams with microphones, headsets and docks is legitimate, so class and interface counts are a triage surface. The Defender variant approximates, as its PnP telemetry reports setup classes, not USB descriptors.
Toshiba-H2C is a string a person typed, so a match is close to conclusive and a miss means nothing. The audio sweep also lists USB audio endpoints, a short but non-empty list on a laptop.
Sources
- Unemployfuscation: Identifying Obfuscated TinyPilot and PiKVM Devices — Grumpy Goose Labs, 2024-11-28
- Detecting Spoofed IPKVMs — chelmzy.tech, 2025-11-22
- Hold Me Closer, TinyPilot — Grumpy Goose Labs, 2025-04-11
- Can anyone detect when I'm using TinyPilot? — TinyPilot, 2026-07-20
- Insights into Suspected DPRK Workers: Red Flags to Look Out For — Huntress, 2026-08-26
- Threat Spotlight: Red Flags for Red Star Hackers: Hunting for North Korean Insiders — ReliaQuest, 2025-05-08