September 4, 2026
Reading the USB Descriptor Tree: From Device to Endpoint
Part 3: How I Turn lsusb -v Output Into an Attack Surface Map
By Gnanamanikandan
6 min read
Part 3: How I Turn lsusb -v Output Into an Attack Surface Map
In the previous article, we moved beyond the basic lsusb output and started looking at what a USB device is actually exposing.
We identified the device.
We looked at its reported identity.
We checked the interfaces.
We mapped the endpoints.
We looked at how the host interpreted the device.
But there is a problem.
A verbose USB descriptor dump can quickly become overwhelming.
You run:
sudo lsusb -v -d <VID>:<PID>sudo lsusb -v -d <VID>:<PID>and suddenly you are looking at dozens โ or sometimes hundreds โ of lines of fields, values, classes, endpoint addresses, and descriptors.
It is tempting to skim through it, search for something that looks unusual, and move on.
That is not how I approach it.
I read the output as a hierarchy.
The most useful mental model is:
Device
โ
Configuration
โ
Interface
โ
EndpointDevice
โ
Configuration
โ
Interface
โ
EndpointEach layer answers a different question.
And together, they tell you how the device is structured and where its communication paths exist.
Start at the Top: The Device Descriptor
The device descriptor gives you the top-level view of the USB device.
This is where I start building the first part of my profile.
Typical fields include:
idVendor
idProduct
bcdUSB
bDeviceClass
bDeviceSubClass
bDeviceProtocol
bNumConfigurations
iManufacturer
iProduct
iSerialidVendor
idProduct
bcdUSB
bDeviceClass
bDeviceSubClass
bDeviceProtocol
bNumConfigurations
iManufacturer
iProduct
iSerialAt this level, I am asking:
- What USB version does the device report?
- How does it identify itself?
- Does it define a class at the device level?
- How many configurations are available?
- Do the descriptor strings match the product I am testing?
But the device descriptor does not tell me everything the device can do.
It gives me the top of the tree.
To understand the functionality, I need to move down.
Configuration: What Can This Device Become?
A USB device can expose one or more configurations.
A configuration describes a particular set of interfaces that can be active.
For example:
Device
โ
โโโ Configuration 1
โ โโโ Interface 0
โ โโโ Interface 1
โ
โโโ Configuration 2
โโโ Interface 0
โโโ Interface 1Device
โ
โโโ Configuration 1
โ โโโ Interface 0
โ โโโ Interface 1
โ
โโโ Configuration 2
โโโ Interface 0
โโโ Interface 1During testing, I check:
bNumConfigurationsbNumConfigurationsand then inspect the configuration descriptors that follow.
The important question is:
Does the device expose more than one operational configuration?
Different configurations can expose different functionality.
That does not automatically mean that every configuration is reachable or active in the same device state.
But from a reconnaissance perspective, multiple configurations deserve attention.
I want to understand:
- Which configuration is currently active?
- What interfaces belong to it?
- Does another configuration expose different functionality?
- Is the behaviour different across normal and special operating modes?
At this stage, I am still mapping.
I am not sending custom requests yet.
Interfaces: Where the Logical Functions Live
This is where the descriptor output usually starts becoming much more interesting.
A USB interface represents a logical function.
For example:
Interface 0 โ CDC Communications
Interface 1 โ CDC DataInterface 0 โ CDC Communications
Interface 1 โ CDC DataOr a device might expose:
Interface 0 โ HID
Interface 1 โ Mass Storage
Interface 2 โ Vendor-SpecificInterface 0 โ HID
Interface 1 โ Mass Storage
Interface 2 โ Vendor-SpecificThis is why I never assume that the physical connector tells me what the device does.
One USB cable can expose multiple logical functions.
When reviewing an interface descriptor, I usually look at:
bInterfaceNumber
bAlternateSetting
bNumEndpoints
bInterfaceClass
bInterfaceSubClass
bInterfaceProtocolbInterfaceNumber
bAlternateSetting
bNumEndpoints
bInterfaceClass
bInterfaceSubClass
bInterfaceProtocolThese fields help answer:
- Which interface am I looking at?
- Does it have alternate settings?
- How many endpoints belong to it?
- What protocol family does it appear to use?
- Is the interface standard or vendor-specific?
One of the most interesting values to encounter is:
bInterfaceClass: 0xffbInterfaceClass: 0xffThat means the interface is vendor-specific.
It does not tell you what protocol is running.
It tells you that the protocol is not defined by a standard USB class.
That is usually where the next stage of analysis becomes more device-specific.
Alternate Settings: Don't Assume One Interface Has One Behaviour
Another field I pay attention to is:
bAlternateSettingbAlternateSettingAn interface can support multiple alternate settings.
Those settings may expose different endpoint arrangements or communication characteristics.
A simplified example might look like:
Interface 1
โ
โโโ Alternate Setting 0
โ โโโ Endpoint 1 IN
โ โโโ Endpoint 2 OUT
โ
โโโ Alternate Setting 1
โโโ Endpoint 3 IN
โโโ Endpoint 4 OUT
โโโ Endpoint 5 INInterface 1
โ
โโโ Alternate Setting 0
โ โโโ Endpoint 1 IN
โ โโโ Endpoint 2 OUT
โ
โโโ Alternate Setting 1
โโโ Endpoint 3 IN
โโโ Endpoint 4 OUT
โโโ Endpoint 5 INIf I only look at one interface entry and assume I have seen the complete communication layout, I may miss other available paths.
This is another reason to read the descriptor output structurally rather than scanning for familiar keywords.
Endpoints: Where Communication Actually Happens
Interfaces define the logical functions.
Endpoints define the communication channels used by those functions.
An endpoint descriptor typically gives me information such as:
bEndpointAddress
bmAttributes
wMaxPacketSize
bIntervalbEndpointAddress
bmAttributes
wMaxPacketSize
bIntervalEach field contributes to the communication map.
Direction
The endpoint address also tells me the direction.
For example:
ININmeans data flows:
Device โ HostDevice โ HostWhile:
OUTOUTmeans:
Host โ DeviceHost โ DeviceFrom a testing perspective, this immediately helps me identify where input may enter the device.
But direction alone is not enough.
I also need to understand the transfer type.
Transfer Type Matters
USB endpoints can use different transfer types.
The common ones include:
- Control
- Bulk
- Interrupt
- Isochronous
Each one has different communication characteristics.
For example:
Control
Used for device management and control operations.
Endpoint 0 is the mandatory control endpoint and is present on every USB device.
Bulk
Used for reliable data transfers where delivery matters.
Bulk endpoints are commonly interesting when analysing device protocols because they may carry larger volumes of application data.
Interrupt
Designed for small, scheduled transfers.
Commonly associated with devices that need timely delivery of relatively small amounts of data.
Isochronous
Used where timing matters more than guaranteed delivery.
Commonly associated with streaming applications.
During reconnaissance, I do not assume that a particular transfer type is secure or insecure.
Instead, I ask:
What data is moving through this endpoint, and how does the firmware process it?
That question belongs to the next phase of testing.
wMaxPacketSize: A Small Field With Practical Importance
Another value I always record is:
wMaxPacketSizewMaxPacketSizeThis tells me the maximum packet size supported by the endpoint.
For Endpoint 0, you may see a value such as:
bMaxPacketSize0: 64bMaxPacketSize0: 64That value matters because it defines part of the communication behaviour expected during control transfers.
For other endpoints, packet size information helps build a clearer picture of how data is transferred.
It can also help explain why a captured protocol behaves differently from what you initially expected.
Again, the value itself is not automatically a security issue.
It is part of understanding the device's communication boundaries.
Turning the Descriptor Dump Into an Attack Surface Map
This is the point where I stop reading individual lines and start building a structure.
For the CDC example from the previous article:
USB Device
โ
โโโ Configuration 1
โ
โโโ Interface 0: CDC Communications
โ โ
โ โโโ Endpoint 5 IN
โ Transfer Type: Interrupt
โ
โโโ Interface 1: CDC Data
โ
โโโ Endpoint 1 IN
โ Transfer Type: Bulk
โ
โโโ Endpoint 2 OUT
Transfer Type: BulkUSB Device
โ
โโโ Configuration 1
โ
โโโ Interface 0: CDC Communications
โ โ
โ โโโ Endpoint 5 IN
โ Transfer Type: Interrupt
โ
โโโ Interface 1: CDC Data
โ
โโโ Endpoint 1 IN
โ Transfer Type: Bulk
โ
โโโ Endpoint 2 OUT
Transfer Type: BulkNow the verbose output is no longer just a collection of descriptor fields.
It becomes a map.
I can see:
- The device exposes one configuration.
- That configuration exposes two interfaces.
- The interfaces work together as a CDC function.
- The device has an interrupt communication path.
- The device accepts bulk data from the host.
- The device returns bulk data to the host.
This immediately tells me where future protocol analysis should focus.
For example:
EP 2 OUT
โ
Host-controlled data enters the device
โ
Firmware parses the data
โ
Application or device logic processes it
โ
Response may be returned through EP 1 INEP 2 OUT
โ
Host-controlled data enters the device
โ
Firmware parses the data
โ
Application or device logic processes it
โ
Response may be returned through EP 1 INThat is not a vulnerability.
It is a communication path.
But communication paths are where input validation eventually matters.
My Descriptor Reading Workflow
When I receive a new lsusb -v output, I usually read it in this order:
1. Device
Record:
- VID/PID
- USB version
- Device class
- Number of configurations
- Descriptor strings
2. Configuration
Identify:
- Number of configurations
- Active configuration
- Power-related information
- Interfaces exposed
3. Interface
For each interface, record:
- Interface number
- Alternate settings
- Class
- Subclass
- Protocol
- Number of endpoints
4. Endpoint
For every endpoint, record:
- Address
- Direction
- Transfer type
- Maximum packet size
- Interval where relevant
Then I turn it into a simple structure.
For example:
Device
โโโ Configuration
โโโ Interface
โ โโโ Endpoint
โ โโโ Endpoint
โ
โโโ Interface
โโโ EndpointDevice
โโโ Configuration
โโโ Interface
โ โโโ Endpoint
โ โโโ Endpoint
โ
โโโ Interface
โโโ EndpointThis gives me something much more useful than a raw descriptor dump.
It gives me a testing map.
The Descriptor Tree Is Not Just Documentation
It is easy to think of USB descriptors as information used only by the operating system.
From a security testing perspective, I see them differently.
The descriptor structure tells me:
- What functionality is exposed.
- How that functionality is organised.
- Where host-controlled data enters.
- Where device responses leave.
- Which interfaces require deeper protocol analysis.
- Which paths may change across configurations or operating modes.
That makes descriptors part of reconnaissance.
Before I send a custom request, capture traffic, or start testing protocol input handling, I want this map.
Because testing without understanding the communication structure usually creates noise.
And noise makes it harder to understand what the device is actually doing.
What Comes Next?
At this point, we know how the device is structured.
We know:
Device
โ
Configuration
โ
Interface
โ
EndpointDevice
โ
Configuration
โ
Interface
โ
EndpointThe next step is to move from:
What is the device exposing?
to:
How does the host actually communicate with it?
In Part 4, I will focus on the USB control plane and Endpoint 0.
We will look at why every USB device exposes a control endpoint, how control transfers are structured, and why EP0 becomes one of the most interesting places to begin active USB protocol analysis.
Because once you understand the map, the next question is simple:
Where do you start talking to the device?