-
Notifications
You must be signed in to change notification settings - Fork 75
ROS 2 DDS Graph Enumeration
Passively and actively enumerate every node, topic, service, and message type in the live ROS 2 graph.
Damn Vulnerable Drone > Attack Scenarios > Reconnaissance > ROS 2 DDS Graph Enumeration
ROS 2 replaces the ROS 1 master with a peer-to-peer DDS discovery protocol (RTPS). Every participant announces itself, its writers (publishers), and its readers (subscribers) on a well-known UDP port range (default 7400 + 250 × domain_id, plus a small offset per participant). Anyone on the network with the right ROS_DOMAIN_ID can enumerate the full graph — there is no authentication by default.
This walkthrough shows two complementary techniques:
-
Passive: sniffing DDS discovery packets with
tcpdump/ Wireshark — no participation in the graph, and no traces left. -
Active: joining the graph as a normal node and listing everything with the
ros2CLI.
The active method is far more readable; the passive method is what you'd use against a target where you can't (or shouldn't) join the graph yourself.
- ROS 2 Humble Docker Image
- ROS 2 CLI documentation
- Wireshark RTPS dissector
- OMG DDS-RTPS spec (port mapping)
⚠️ Solution Guide
NET_RAW is needed so tcpdump can open a raw socket.
docker pull osrf/ros:humble-desktop
docker run -it --network=simulator --ip=10.13.0.10 \
--cap-add NET_ADMIN --cap-add NET_RAW \
--name dds_enumerator osrf/ros:humble-desktop bash
DDS discovery ports are domain-dependent: 7400 + 250 × domain_id (plus a small per-participant offset). The lab runs ROS_DOMAIN_ID=42, so discovery lands at 7400 + 250×42 = 17900 — the lab containers listen on 17910–17915. The common 7400–7700 filter is the domain-0 range and captures nothing here. Listen across the domain-42 discovery range instead:
apt-get update && apt-get install -y tcpdump
tcpdump -i any 'udp portrange 17900-18200' -nn -v
You should see UDP traffic between 10.13.0.3 (companion-computer) and 10.13.0.5 (simulator). Each burst contains:
-
DATA(p)participant announcements (one per node) -
DATA(w)writer (publisher) announcements (one per topic published) -
DATA(r)reader (subscriber) announcements (one per topic subscribed)
You can identify these by their RTPS submessage IDs, but tcpdump alone won't decode the payload. For payload inspection, use Wireshark with the RTPS dissector (see Step 5).
Configure the attacker container so it joins the same domain and Cyclone DDS unicast graph as the lab:
# osrf/ros:humble-desktop ships only rmw_fastrtps_cpp; install the Cyclone RMW
# or every ros2 command aborts with "librmw_cyclonedds_cpp.so: cannot open
# shared object file".
apt-get update && apt-get install -y ros-humble-rmw-cyclonedds-cpp
cat > /etc/cyclonedds.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<CycloneDDS xmlns="https://cdds.io/config">
<Domain id="any">
<General>
<AllowMulticast>false</AllowMulticast>
</General>
<Discovery>
<Peers>
<Peer address="10.13.0.3"/>
<Peer address="10.13.0.5"/>
</Peers>
</Discovery>
</Domain>
</CycloneDDS>
EOF
export ROS_DOMAIN_ID=42
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export CYCLONEDDS_URI=file:///etc/cyclonedds.xml
source /opt/ros/humble/setup.bash
Now enumerate:
ros2 node list
ros2 topic list
ros2 service list
ros2 action list
ros2 topic info /webcam/image_raw
ros2 topic type /webcam/image_raw
ros2 interface show sensor_msgs/msg/Image
ros2 topic echo /webcam/image_raw --no-arr | head -20
ros2 topic info lists the publishers and subscribers with their QoS settings (which you'll need to match if you want to spoof — see the ROS 2 Rogue Publisher Injection scenario).
From a host with Wireshark installed and access to the simulator bridge:
sudo wireshark -k -i docker0 -Y rtps
The rtps display filter shows only DDS-RTPS traffic. Drill into a DATA(p) packet to see the participant GUID, vendor ID, and built-in endpoint mask. Drill into DATA(w) packets to see the topic name, type name, and the full QoS policy block (reliability, durability, history, etc.) — useful for crafting an injector that QoS-matches the legitimate publisher.
A vanilla ROS 2 deployment has no defence against any of the above. Defences are:
- Run on a segmented network — the attacker has to land on the same L2 broadcast domain (or be listed as a unicast peer) to participate.
- Enable SROS 2 — every participant must present a valid keystore-signed identity. But SROS 2 has its own attack surface — the keystore is usually filesystem-readable to any process on the robot, and there is no certificate revocation flow.
-
-
Reconnaissance
-
Protocol Tampering
-
Denial of Service
-
Injection
-
Exfiltration
-
Firmware Attacks
-
-
Learning Resources