Skip to content

ossac/tplink-sg3428-usb-reset

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

 ╔══════════════════════════════════════════════════════════════════════╗
 ║                                                                      ║
 ║   ████████╗██████╗       ██╗     ██╗███╗   ██╗██╗  ██╗              ║
 ║   ╚══██╔══╝██╔══██╗      ██║     ██║████╗  ██║██║ ██╔╝              ║
 ║      ██║   ██████╔╝█████╗██║     ██║██╔██╗ ██║█████╔╝               ║
 ║      ██║   ██╔═══╝ ╚════╝██║     ██║██║╚██╗██║██╔═╝                ║
 ║      ██║   ██║            ███████╗██║██║ ╚████║██║                   ║
 ║      ╚═╝   ╚═╝            ╚══════╝╚═╝╚═╝  ╚═══╝╚═╝                ║
 ║                                                                      ║
 ║   USB CONSOLE HACK                                                   ║
 ║   Linux driver for TP-Link JetStream switch console port             ║
 ║                                                                      ║
 ╚══════════════════════════════════════════════════════════════════════╝

The Problem

TP-Link ships their JetStream managed switches (SG3428, SG3428X, SG3428MP, SG2218, etc.) with a micro-USB console port for CLI access. One problem:

There is no Linux driver.

The USB chip inside uses VID:2357 PID:0701 with a proprietary protocol. TP-Link only provides a Windows driver (CypressUsbConsoleWindowsDriver.sys). Linux kernel modules (cp210x, ch341, ftdi_sio, cdc_acm, usbserial) all fail:

Driver Result
cp210x cp210x_open - Unable to enable UART
cdc_acm Rejected (device class 0xFF, not 0x02)
ch341 Not detected
ftdi_sio Not detected
usbserial (generic) Loads, creates /dev/ttyUSB0, but cannot set baud rate

The generic usbserial driver creates a device node but all data comes through as garbage because the baud rate is stuck at whatever the chip defaults to.

The Solution

We reverse-engineered the USB protocol by brute-forcing control transfers across every known serial chip protocol:

CH341  (0x5F READ_VERSION)       -> STALL
CP210x (0x04 GET_LINE_CTL)       -> STALL
FTDI   (0x05 MODEM_STATUS)       -> STALL
PL2303 (0x8484 VENDOR_READ)      -> STALL
Cypress (0xB0 GET_VERSION)       -> STALL
CDC ACM (0x21 GET_LINE_CODING)   -> 9600/8N1   <<<< BINGO

The chip responds to CDC ACM class requests (GET_LINE_CODING, SET_LINE_CODING) even though it declares itself as Vendor Specific (class 0xFF) in its USB descriptor.

This means we can:

  1. Send SET_LINE_CODING (bmRequestType=0x21, bRequest=0x20) via libusb to set 38400/8N1
  2. Read/write data through bulk endpoints (OUT: 0x02, IN: 0x83)
  3. Get a fully functional serial console without any kernel driver

USB Device Details

$ lsusb -v -d 2357:0701

Bus 001 Device 002: ID 2357:0701 TP-Link TP-Link USB Serial COM Port
  bcdUSB               2.00
  bDeviceClass            0     (Defined at Interface level)
  idVendor           0x2357     TP-Link
  idProduct          0x0701
  bcdDevice            6.05

  Interface Descriptor:
    bInterfaceClass       255    Vendor Specific Class     <-- THIS IS THE LIE
    bInterfaceSubClass      0
    bNumEndpoints           3

    Endpoint 0x81  EP 1 IN   Interrupt
    Endpoint 0x02  EP 2 OUT  Bulk        (Host -> Switch)
    Endpoint 0x83  EP 3 IN   Bulk        (Switch -> Host)

The key insight: bInterfaceClass = 255 prevents automatic driver binding, but the firmware actually implements CDC ACM class requests. Someone at TP-Link configured the USB descriptor to say "Vendor Specific" when the chip actually speaks a standard protocol.

Installation

# Dependencies (Debian/Ubuntu)
sudo apt install python3-usb libusb-1.0-0

# Clone
git clone https://github.com/YOUR_USERNAME/tplink-usb-console-hack.git
cd tplink-usb-console-hack

# Run (requires root for USB access)
sudo python3 tplink_console.py interactive

Optional: udev rule (run without sudo)

# Create udev rule
sudo tee /etc/udev/rules.d/99-tplink-console.rules << 'EOF'
SUBSYSTEM=="usb", ATTR{idVendor}=="2357", ATTR{idProduct}=="0701", MODE="0666"
EOF

# Reload
sudo udevadm control --reload-rules && sudo udevadm trigger

Usage

Interactive Terminal

sudo python3 tplink_console.py interactive

Full bidirectional terminal. Type commands, see responses. Like minicom but without needing a kernel driver. Exit with Ctrl+C.

Baud Rate Scanner

sudo python3 tplink_console.py scan

Tests all common baud rates (9600, 19200, 38400, 57600, 115200) and reports which one produces readable output:

   9600 baud:    3 bytes,   0% readable
  19200 baud:    2 bytes,   0% readable
  38400 baud:  300 bytes, 100% readable  <<<< MATCH
  57600 baud:    2 bytes,   0% readable
 115200 baud:    2 bytes,   0% readable

Chip Fingerprinting

sudo python3 tplink_console.py probe

Identifies which USB serial protocol the chip supports by testing vendor-specific control transfers:

  [-] CH341    READ_VERSION         -> STALL
  [-] CP210x   GET_LINE_CTL         -> STALL
  [-] FTDI     MODEM_STATUS         -> STALL
  [-] PL2303   VENDOR_READ          -> STALL
  [-] Cypress  GET_VERSION          -> STALL
  [+] CDC ACM  GET_LINE_CODING      -> 009600 / 8N1

Factory Reset (BootUtil)

sudo python3 tplink_console.py reset

Full automated factory reset via the switch's BootUtil menu:

  1. Power off the switch
  2. Run the command
  3. Power on the switch when prompted
  4. Tool automatically sends Ctrl+B to intercept bootloader
  5. Executes Password Recovery (option 6) + Factory Reset (option 2)
  6. Reboots the switch

Note: BootUtil's Y/N confirmation prompts reject standard \r\n line endings with "Input string contains illegal characters". The workaround (discovered through trial and error) is sending the menu option with \r immediately followed by Y\r with only a 100ms delay.

Send Single Command

sudo python3 tplink_console.py send "show system-info"
sudo python3 tplink_console.py send "show interface vlan 1"

How It Works

┌──────────────────────────────────────────────────────────────┐
│                      tplink_console.py                       │
│                                                              │
│  1. Find USB device (VID:2357 PID:0701) via libusb           │
│  2. Detach kernel driver (usbserial_generic)                 │
│  3. Claim USB interface 0                                    │
│  4. Send CDC ACM SET_LINE_CODING:                            │
│     bmRequestType = 0x21 (Class, Interface, Host-to-Device)  │
│     bRequest      = 0x20 (SET_LINE_CODING)                   │
│     wValue        = 0                                        │
│     wIndex        = 0                                        │
│     data          = {38400, 0, 0, 8}  (baud, stop, par, bits)│
│  5. Read/Write via bulk endpoints:                           │
│     EP 0x02 OUT (Bulk) -> Commands to switch                 │
│     EP 0x83 IN  (Bulk) -> Responses from switch              │
└──────────────────────────────────────────────────────────────┘

TP-Link Console Quick Reference

Parameter Value
Baud Rate 38400 (not 9600, not 115200)
Data Bits 8
Stop Bits 1
Parity None
Flow Control None
Default IP 192.168.0.1
Default Credentials admin / admin
BootUtil Key Ctrl+B during boot
BootUtil Reset Option 2 (full reset) or Option 6 (password only)

Tested Hardware

  • TP-Link TL-SG3428 v2.20 (JetStream L2+ 28-Port Managed Switch)
  • BootUtil v1.0.0 (firmware Feb 13 2025)

Should work on any TP-Link switch with USB console VID:2357 PID:0701, including:

  • TL-SG3428X
  • TL-SG3428MP
  • TL-SG3428XMP
  • TL-SG2218
  • Other JetStream/Omada models with micro-USB console

The Investigation Log

For those curious about the full reverse-engineering process:

  1. Initial attempt: cp210x driver loaded, found device, created /dev/ttyUSB0, then failed with "cp210x_open - Unable to enable UART". Dead end.

  2. Generic driver: modprobe usbserial vendor=0x2357 product=0x0701 created /dev/ttyUSB0 but stty couldn't configure it. picocom showed garbled j§k§k§k§ at every baud rate. The driver was reading data but couldn't change the UART speed.

  3. The same garbage at all baud rates was the key clue: the generic driver ignores baud rate changes for this device. It always reads at the chip's internal default.

  4. Chip fingerprinting: Wrote a brute-force script that tries every known vendor-specific USB control transfer. CH341, CP210x, FTDI, PL2303, Cypress all returned STALL. But CDC ACM GET_LINE_CODING returned 9600/8N1. The chip was lying about its USB class.

  5. SET_LINE_CODING to 38400: Once we could configure the baud rate via libusb, everything worked. 300 bytes of clean ASCII from the switch console.

  6. BootUtil line ending bug: The switch's BootUtil menu accepts \r\n for numbered menu options but rejects it for Y/N confirmations with "Input string contains illegal characters". Only \r works, and it must be sent with precise timing (100ms after the menu selection).

License

MIT License. Do what you want. No warranty. If you brick your switch, that's between you and TP-Link support.

Credits

  • OSSAC (Osmiotech Seguridad Automatización y Control S.A.S.)
  • Built with Claude Code - February 2026
  • "We didn't hack the switch. We wrote the driver they didn't ship."

About

Factory Reset TP-Link SG3428 JetStream via USB Console on Linux. No Windows needed - Python tool bypasses missing kernel driver using raw USB (CDC ACM). Includes: interactive terminal, baud scanner, chip fingerprinting, automated BootUtil factory reset.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages