Sniffing USB with Cynthion on a DP100 power supply

last updated: 2025-05-20

Quick links

Cythion
click for a better view

Intro

Mid 2021 I backed the promising Luna hardware from Great Scott Gadgets on crowd supply. In february 2023 Luna hardware was renamed to Cynthion and my Cynthion was shipped mid 2024. I will use Cynthion here as USB 2.0 protocol analyzer (Low, Full, and High Speed).

Hardware to analyze: ALIENTEC DP100 power supply

The DP100 is a tiny power supply with 2 USB connectors. The USB-C connector is the power input up to 32 V, 5 A with an USB-PD power supply (or another DC alimentation up to 32 V). It has a tiny OLED display, three buttons and an adjustment wheel.

The second USB-A connector can also be used to provide power (5 V/1 A) but is primarily needed to connect the device to a PC and to use the PC Windows software.

I don't use Windows, and I wanted a software for Linux especially as the usage of the device itself is not very intuitive and the buttons and the display are tiny (if you are only interested in the software look here: https://www.weigu.lu/other_projects/python_coding/dp100_manipulator/index.html).

To create a software, we need to understand the proprietary protocol used on USB.

So let's start :)

Setting up the Cynthion hardware

First we clone the repository to our favourite folder:

    git clone https://github.com/greatscottgadgets/cynthion.git

There is a good documentation on readthedocs: https://cynthion.readthedocs.io/en/latest/. So let's concentrate on the essential.

Connect the Cynthion device with the host control computer (running the software, man in the middle). Use the USB-C socket labelled CONTROL.

To avoid doing everything as root, we copy the udev rules to /etc/udev/rules.d, reload the rules and apply them to the devices already plugged in:

    cd cynthion
    sudo cp cynthion/python/assets/54-cynthion.rules /etc/udev/rules.d
    sudo udevadm control --reload
    sudo udevadm trigger

The Cynthion software is written in Python. So we use a virtual environment to deploy it. More on virtual environments: https://www.weigu.lu/other_projects/python_coding/using_venv/index.html.

    python3 -m venv cynthion-venv
    source cynthion-venv/bin/activate
    pip3 install --upgrade cynthion

Now let's test if everything works as expected:

    cynthion info --force-offline

Perhaps we need to update to the latest firmware:

    cynthion update
    cynthion info

As Cynthion can do different things, we need to configure it as analyzer by loading the right bitstream to the FPGA:

    cynthion run analyzer

To use the USB Analyzer as the default bitstream for the FPGA we can flash it to the FPGA:

    cynthion flash analyzer
    cynthion info

Now we can leave the virtual environment with deactivate. To reuse the cynthion hardware software we do again a source cynthion-venv/bin/activate.

Setting up Packetry, the analyzing software

The analyzing software is called Packetry and is written in Rust and uses GTK4. Packetry is fast and analysis the hardware USB bus packets. It can display them in a hierarchical way, that facilitates the interpretation of the traffic. You can save also the data as pcap-file and look at it with wireshark. But wireshark is less suitable to analyse USB data You can find explanations in the ShmooCon 2025 video.

The simplest way is to use an AppImage: https://github.com/greatscottgadgets/packetry/releases.

But its also interesting to learn how to run a Rust program on Linux. First we clone the repo:

    git clone https://github.com/greatscottgadgets/packetry.git
    cd packetry

Then we install rustc with the rustup tool and build Packetry with cargo:

    sudo apt install rustup build-essential libgtk-4-dev
    rustup default stable
    rustc -V
    cargo build --release

The Packetry binary is now in the folder target/release/.

I had two problems with Packetry.

After solving these problems, I was able to sniff the traffic.

Logging the traffic

Enumeration

We connect the PC with the software for our USB device (for me a Windows machine in Virtualbox) to the USB-C port labelled TARGET C. Now we start the record process in Packetry (red dot). After that we connect the USB device to the USB A port labelled TARGET A. After some seconds we can stop and save the enumeration process in a pcap-file.

Packetry
Click for a better view!

If we get nothing, the speed is wrong. Set the speed to low, full or high according to your device. If you don't know the speed, try one after the other. The DP-100 used here works with full speed (12 Mbps).

Ok, we could use lsusb to get the device data, but in the device window it is easier to check than in a terminal. What do we have here?

The Manufacturer string is ALIENTEK, Product string ATK-MDP100, Vendor ID:0x2E3C, Product ID: 0xAF01

This helps already to write a udev rule for Linux, so that we don't need to be root to access the device.

    cd /etc/udev/rules.d
    sudo nano 55-ATK-MDP100.rules

Copy the following lines in the editor and save (Ctrl+o, Ctrl+x) and reload the rules.

# DP100
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2e3c", ATTRS{idProduct}=="af01", MODE="0660", GROUP="plugdev"
    sudo udevadm control --reload
    sudo udevadm trigger

We can test test the udev rules with:

    udevadm test /sys/class/hidraw/hidraw0
    ls -l /dev/hidraw0

We get one configuration with one HID interface. The HID Interface has a descriptor with 32 bytes. Let's ask KI (chat mistral) to get the meanings and create a table. Here the result:

Byte Index Value (Hex) Description
0-1 06 FF Usage Page (Global Item) - Vendor-defined usage page (0xFF).
2 00 Reserved - Reserved byte, typically set to 0x00.
3-4 09 01 Usage (Local Item) - Vendor-defined usage (0x01).
5-6 A1 01 Collection (Main Item) - Begins an application collection.
7-8 15 00 Logical Minimum (Global Item) - Minimum value is 0x00.
9-10 25 FF Logical Maximum (Global Item) - Maximum value is 0xFF.
11-12 75 08 Report Size (Global Item) - Size is 8 bits.
13-14 95 40 Report Count (Global Item) - Count is 64 fields.
15-16 09 01 Usage (Local Item) - Vendor-defined usage (0x01).
17-18 81 02 Input (Main Item) - Defines an input report (Data, Variable, Absolute).
19-20 95 40 Report Count (Global Item) - Count is 64 fields.
21-22 09 01 Usage (Local Item) - Vendor-defined usage (0x01).
23-24 91 02 Output (Main Item) - Defines an output report (Data, Variable, Absolute).
25-26 95 01 Report Count (Global Item) - Count is 1 field.
27-28 09 01 Usage (Local Item) - Vendor-defined usage (0x01).
29-30 B1 02 Feature (Main Item) - Defines a feature report (Data, Variable, Absolute).
31 C0 End Collection (Main Item) - Ends the collection.

We have a vendor designed usage with an application collection. No HID class is used. HID is used to avoid a driver. The exchange is done with a field of 64 byte (8 bit). The meanings are only known to the vendor.

Data exchange

Next we use the windows software and connect to the device, switch on and off, and disconnect again. By analyzing the IN and OUT transactions (we have only 2 endpoints), we see that the host PC sends always 0xFB as first byte (OUT) and the device 0xFA (IN). If we look at short commands like:

    FB, 30, 00, 00, 31, 0F,
    FA, 35, 00, 01, 01, 33, 88,

we can guess that the second byte from the host is perhaps a command. In the first OUT transactions we see 0x30, 0x10, 0x35 and 0x40. The byte is repeated by the device if it answers. The third byte is always 0x00. The fourth byte (host) is 0x00 followed by 2 byte or 0x01 followed by 3 byte, so perhaps the length and a checksum. I asked again the KI and it took a bit longer to find that we have indeed the length of the following data followed by a checksum called CRC-16-Modbus. What it made it harder to find this was the fact, that the lowest byte comes first also called as little endian.

Byte 0 1 2 3 4–3+n 4+n 5+n
Meaning 0xfb/0xfa command 0x00 len data n ... crcL crcH

Basic Info

Now it helps to use the Transaction tab of Packetry to see the answer to a command. We see that the first answer of the device is:

    FA, 30, 00, 10,
    C8, 4E, 00, 00, 00, 00, 5E, 4C, 2F, 01, 2D, 01, CE, 13, 02, 00,
    92, 29,
    0E, 00, 0B, 00, AA, 00, 15, 89, 64, 12, 00, 00, 27, 70, 1D, 27,
    D9, 05, E8, 07, 08, 1D, 81, 49, 00, 00, 00, 00, 00, 00, 00, 00,
    00, 00, 00, 00, 00, 00, 00, 00, 00, 00

Ok strange. The length is 0x10, so 16 byte. I use an online calculator for the crc (crccalc.com) and yes, the result is 0x2992 for the first 19 byte. This fits with LB first (little endian). I assume we can ignore the following byte?

Now let's also assume that the data uses 2 byte (unsigned integer) and we use little endian. In decimal we get:

    20168, 0, 0, 19550, 303, 301, 5070, 2

Hey cool. By comparing with the win software we find the input voltage (20.16 V), perhaps the output voltage and current (0) and the system temperature (30.3 °C).

Ok let's connect a resistor and activate the Output.

    FA, 30, 00, 10,
    BD, 4E, 8D, 13, 17, 00, 5E, 4C, 43, 01, 48, 01, CD, 13, 01, 00,
    F9, 48,

The CRC is ok. Now we get

    19901, 5005, 23, 19550, 323, 328, 5069, 1

This shows now we definitively have the output voltage of 5 V and a current of 23 mA.

So the 0x30 command returns basic information. It seems we get two temperatures and also 2 additional voltages (19.55 V, 5,069 V (USB voltage?))

From host (PC):

Byte 0 1 2 3 4 5
Basic Set 0xfb 0x30 0x00 0x00 crc 0x31 crc 0x0f

From device (DP100):

Byte 0 1 2 3 4–5 6–7 8–9 10–19 20–21
Basic Info 0xfa 0x30 0x00 0x10 vin vout iout ... crc

Device Info

Next let's look at the answer of the 0x10 command:

    FA, 10, 00, 28,
    41, 54, 4B, 2D, 44, 50, 31, 30, 30, 00, FF, FF, FF, FF, FF, FF,
    0E, 00,
    0E, 00,
    0B, 00,
    AA, 00,
    15, 89, 64, 12, 00, 00, 27, 70, 1D, 27, D9, 05,
    E8, 07, 08, 1D,
    81, 49

There is some ASCII code! The first 9 byte give "ATP-DP100" :). So we have here device infos. Next we see two times 0x0E = 14. My hardware version is 1.4 and also the software version is 1.4. My serial number is 1D27D905.

From host (PC):

Byte 0 1 2 3 4 5
Basic Set 0xfb 0x10 0x00 0x00 crc 0x30 crc 0xc5

From device (DP100):

Byte 0 1 2 3 4–19 20–21 22–3 24–35 36–39 40–43 44–45
Device Info 0xfa 0x10 0x00 0x28 name hver sver ... serial nr ... crc

From here on I cheated and looked in an older article in the french Hackable mag, where Denis Bodor already worked on the DP100 USB communication. And it was good that I cheated, because the command 0x35 (Basic Set) is very complicated and not intuitive at all. Even with all the available information I had to use Cynthion to really understand what is happening.

Denis found the following file from lessu, that helped a lot: https://github.com/lessu/open_dp100/blob/master/DP100_Protocol.md.

So let's look at this weird command.

The DP100 has 10 profiles numbered from 0–9 with voltage voset and current ioset. One of this profiles is active and is used if we switch on. So we have no command to change the voltage or current without using the profiles!

The complicated command 0x35

Get active profile

If we send the command 0x35 with the data byte 0x80 we get the data for the active profile. This is the only command to know which profile is active!

From host (PC):

Byte 0 1 2 3 4 5 6
Basic Set 0xfb 0x35 0x00 0x01 0x80 crc 0xce crc 0x28

From device (DP100):

Byte 0 1 2 3 4 5 6–7 8–9 10–11 12–13 14–15
Basic Set 0xfa 0x35 0x00 0xa index state vo_set io_set OVP OCP crc

Index gives us this information. The second data byte state informs us if the output is on or off!

Get all profiles

If we replace 0x80 with 0x000x09 we can read voset and ioset for all profiles.

Change a profile

To change a profile, we need to read the profile and save the data. Then we change the first data byte (byte 4) and the values for voltage and current. We recalculate the CRC and send the data back. The upper nibble of byte 4 must be 0x4 and the lower nibble is the profile number (e.g. to change third profile (number 2) we send 0x42).

Activate a profile

If we want to change voltage or/and current we need to activate the profile with the wanted values or even first set the values to a profile and than activate it. To do so we need to read the profile and save the data. Then we change the first data byte (byte 4), recalculate the CRC and send the data back. The upper nibble of byte 4 must be 0xA and the lower nibble is the profile number (e.g. to activate the forth profile (number 3) we send 0x83).

Switch ON and OFF

To switch on/off we have to look which profile is activated. Then we read this profile, save the data and change the first and the second data byte (Byte 4 and 5), recalculate the CRC and send the data back.

The upper nibble of byte 4 must be 0x2 and the lower nibble is the profile number (e.g. to switch on/off the active profile number 5 we send 0x25). Byte 5 is 0x01 to switch on and 0x00 to switch off.

My python software for the DP100 can be found here: https://github.com/weigu1/dp100_manipulator, and information about the software here: https://www.weigu.lu/other_projects/python_coding/dp100_manipulator/index.html

Interesting links