A Wireshark dissector for DVRIP/Sofia protocol found in Xiongmai based IP cameras. Full writeup of a sample IP camera is available at Besder 6024PB-XMA501 IP camera security investigation repository.
This dissector is based on a DVRIP Wireshark Dissector for Port TCP/37777 (Dahua IP camera), which can be found here: https://github.com/r4bit999/dvrip-analysis/tree/master
Copy dvripWireshark.lua to Wireshark plugin directory:
cp dvripWireshark.lua /usr/lib/wireshark/plugins/
DVRIP/Sofia protocol found in Xiongmai-based IP cameras run on the following ports:
TCP/34567for local controls and media stream.TCP/6611for cloud controls and media stream.UDP/34569andUDP/34571for local configs.UDP/7999andUDP/8765for cloud configs.
Tested on Besder 6024PB-XMA501 IP camera:
Model: XM530_50X50-WG_8M
Firmware version: V5.00.R02.00030747.10010.349f17
Aside from the main DVRIP/Sofia message header (which is in little-endian (LE) format), the protocol has additional headers for the following payloads:
- I-Frame payloads
- P-Frame payloads
- Audio payloads
- Snapshot payloads
- Information frame payloads
All non-JSON payload header fields have their values in little-endian order (except for the first 4 header bytes that are in big-endian (BE) order and represent the payload type).
Media payload headers were reconstructed based on Xiongmai bitstream frame format document.
Parsing of DVRIP/Sofia message header is based on Digital Video Recorder Interface Protocol (DVRIP) document, the actual diagram being on page 7.
A diagram depicting DVRIP/Sofia message header is presented below:
- BYTE 0: message header byte, fixed as 0xFF.
- BYTE 1: observed to be equal to 0 for requests and equal to 1 for responses from the IP camera.
- BYTE 2: reserved byte 1:
- Equals
0when H.264 video codec is used (BYTE 4 =0x02on I-Frame header). - Equals
1when H.265 video codec is used (BYTE 4 =0x12on I-Frame header).
- Equals
- BYTE 3: reserved byte 2:
- Equals
128when DVRIP message contains audio frames. - Otherwise the value equals
0.
- Equals
- BYTES 4-7: session ID. Assigned by the camera after successful login. Needs to be present in every subsequent message.
- BYTES 8-11: sequence number. Increments from 0 after startup, and after reaching the (unknown) maximum, starts from 0 again.
- BYTE 12: total number of messages in a single packet. Value of 0 or 1 indicates a single message per packet.
- BYTE 13: number of a current message in a packet. Meaningful only when the value of total packets (BYTE 12) is greater than 1.
- BYTES 14-15: command code (also called message id). The code defines what action to perform.
- BYTES 16-19: data (payload) length of a given DVRIP message.
DVRIP message header, as represented in Wireshark:
DVRIP audio payload has its own header, depicted below:
- BYTES 0-3: signature
- BYTE 4: audio codec (0x0e = G711A)
- BYTE 5: sampling rate (0x02 = 8kHz sampling)
- BYTES 6-7: length of audio payload
Header of DVRIP audio payload, as depicted in Wireshark:
In video encoding, I-Frame (or intra-coded picture) is a standalone image that is used as a visual information base for a chain of surrounding predicted pictures (P-Frames) and bidirectional predicted pictures (B-Frames) - both of which store only changes from a referenced previous image.
Diagram, depicting the header of an I-Frame payload:
- BYTES 0-3: signature
- BYTE 4: video codec (0x01 = MPEG4, 0x02 = H.264, 0x12 = H.265)
- BYTE 5: encoded framerate (variable; 1-25 for PAL, 1-30 for NTSC)
- BYTE 6: low 8 bits of image width; the value is actual width divided by 8
- BYTE 7: low 8 bits of image height; the value is actual height divided by 8
- BYTES 8-11: capture date
- BYTES 12-15: length of I-Frame payload
I-Frame header, as depicted in Wireshark:
Transmitted I-Frames are stored in Network Abstraction Layer Units (NALUs) and the start of every I-Frame is defined by Annex-B (0x00000001) prefix.
Same header structure is shared between I-Frames (FC) and snapshots (FE).
Diagram, depicting the header of a P-Frame payload:
- BYTES 0-3: signature
- BYTES 4-7: length of P-Frame payload
P-Frame header, as depicted in Wireshark:
Same as with I-Frames, P-Frames of a captured stream are stored in NALUs with every unit identified by Annex-B prefix.
No B-Frames were captured while testing the Wireshark dissector.
Diagram depicting the header of an information frame payload:
- BYTES 0-3: signature
- BYTE 4: general information (unconfirmed)
- BYTE 5: unused value
- BYTES 6-7: payload length
Information frame as depicted in Wireshark:
Used for information transmission. First byte after signature is observed to be either one of the following:
- 0x01 - general information.
- 0x06 - unknown value.
Tested on Linux only!
dvrip_video_reconstruction.lua is a slightly older version of this dissector that is programmed to reconstruct audio and video streams from the packet capture (.pcap) file. Copy this file to /usr/lib/wireshark/plugins, then start Wireshark. In it's GUI, navigate to Tools menu and select DVRIP Save Streams entry. In the pop-up dialog box, enter the desired folder to save the streams.
File names of saved streams are structured as follows:
<camera-ip-address>_<reserved-byte-1>_<reserved-byte-2>_<audio|video>.<g711|h265>
DVRIP/Sofia protocol fields used in this protocol dissector:
| Field Name | Filter Name | Description |
|---|---|---|
| DVRIP_header | dvrip.header | Full DVRIP/Sofia header |
| DVRIP_header_id | dvrip.header_id | First byte of DVRIP header, observed to be 0xFF |
| DVRIP_req_resp | dvrip.req_resp | Request/response byte. 0x00 for request and 0x01 for response |
| DVRIP_reserved_1 | dvrip.reserved_1 | Reserved byte 1. Indicates video stream |
| DVRIP_reserved_2 | dvrip.reserved_2 | Reserved byte 2. Indicates audio stream |
| DVRIP_session_id | dvrip.session_id | ID of an established session |
| DVRIP_sequence_id | dvrip.sequence_id | Sequence ID. Message number in the current session |
| DVRIP_total_packets | dvrip.total_packets | Number of messages in a single packet. 0 or 1 indicate a single message |
| DVRIP_current_packet | dvrip.current_packet | Current message in a packet. Meaningful only when total packets > 1 |
| DVRIP_command_code | dvrip.command_code | Command code/Message ID. Identifies an action to perform |
| DVRIP_payload_size | dvrip.payload_size | Payload size of a current message |
| DVRIP_payload_JSON_RAW | dvrip.data | JSON data payload. Starts immediately after header |
| DVRIP_newline | dvrip.newline | Trailing newline after JSON payload |
| DVRIP_cloud_ip | dvrip.cloud_ip | IP address of cloud relay |
| DVRIP_home_ip | dvrip.home_ip | Public IP address of a home network |
| DVRIP_device_id | dvrip.device_id | Serial number of IP camera |
| DVRIP_device_mac | dvrip.device_mac_address | MAC address of IP camera |
| DVRIP_encrypted | dvrip.encrypted | Encrypted payload of a message |
| DVRIP_signature | dvrip.signature | Signature of media frame |
| DVRIP_stream_type | dvrip.stream_type | Video stream type |
| DVRIP_framerate | dvrip.framerate | Stream framerate |
| DVRIP_width | dvrip.width | Width of video frame (divided by 8) |
| DVRIP_height | dvrip.height | Height of video frame (divided by 8) |
| DVRIP_datetime | dvrip.datetime | Start date of the stream |
| DVRIP_media_payload_size | dvrip.media_payload_size | Payload size of media frame |
| DVRIP_sampling_rate | dvrip.sampling_rate | Audio sampling rate |
| DVRIP_unused_field | dvrip.unused_field | Unused field in information frame header |









