DVRIP/Sofia protocol dissector for Wireshark.
Wireshark protocol dissector written in Lua aimed at analysis of a DVRIP ( sometimes also called Sofia) proprietary communication protocol found in Xiongmai-based IP cameras and running on port 34567/TCP (some network packets associated with this protocol were observed on port 34569/UDP).
This dissector is based and built upon a DVRIP protocol dissector for Dahua IP cameras, built by Thomas Vogt (r4bit999).
Full dissector code is available on the project’s Github repository.
Table of Contents
- Introduction
- DVRIP/Sofia Message Header
- JSON Payload
- Media Frames
* I-Frame
* P-Frame
* Audio Frame
* Information Frame - Additional Features
* Stream Muxing
Introduction
DVRIP/Sofia dissector is part of a security assessment of an old and cheap Chinese IP camera (Besder 6024PB-XMA501), and, as previously mentioned, is based on existing dissector for Dahua’s DVRIP implementation. Although similar, DVRIP protocols between these vendors are not identical. For one, a single message on Dahua’s DVRIP implementation has 32 bit length header, while on Xiongmai-based IP cameras the header length of a message is observed to be 20 bits.
Main functionality of DVRIP/Sofia dissector is as follows and will be presented in subsequent sections:
- Dissect a header of DVRIP/Sofia messages into separate fields based on an old Digital Video Recorder Interface Protocol document from Hangzhou Xiongmai Information Co., Ltd.
- Parse a JSON payload on each DVRIP/Sofia message (used for camera controls and responses).
- Describe different types of media frames that were observed during testing. Each type of frame has it’s own header and an unique signature.
- Combine I-Frames and P-Frames into a video stream while rebuilding a concurrent audio stream from audio frame payloads.
- Exporting both video and audio streams to /tmp directory for external multiplexing via ffmpeg.
DVRIP/Sofia Message Header
A DVRIP/Sofia message on Xiongmai-based devices has a 20 bit long header with the following structure:

This header is taken from DVRIP keep alive request message that was sent to the IP camera for session maintenance. In Wireshark, DVRIP header bytes are saved in big-endian order, however when reading individual fields, byte order needs to be reversed to little-endian (LE) for correct parsing. The following is how the header is depicted in Wireshark:

10 fields in total were identified on this header:
- BIT 0: indicates start of DVRIP message. Always observed to be 0xff.
- BIT 1: observed as 0x00 for request messages and 0x01 for responses.
- BIT 2: Reserved byte №1:
a. Equals 0 when H.264 video codec is used (BIT4 = 0x02 on I-Frame header).
b. Equals 1 when H.265 video codec is used (BIT4 = 0x12 on I-Frame header). - BIT 3: Reserved byte №2:
a. Equals 128 when DVRIP/Sofia message contains audio frames.
b. Equals 0 otherwise. - BIT 4–7: session ID. Given to a session after a successful login.
- BIT 8–11: sequence number. Increments from 0 after startup, and after reaching the (unknown) maximum, starts from 0 again.
- BIT 12: total number of packets in a single message. Value of 0 or 1 indicate a single message per packet.
- BIT 13: number of a current packet in message. Meaningful only when the value of total packets (BIT 12) is greater than 1.
- BIT 14–15: command code (also called message id). The code defines what action to perform.
- BIT 16–19: data (payload) length. Length of a JSON payload, which starts immediately after DVRIP/Sofia header.
JSON Payload
Aside from DVRIP/Sofia message header, the aforementioned keep alive request includes a JSON payload:
{
"Name" : "KeepAlive",
"SessionID" : "0x00000006"
}IP camera responds with the following:
{
"Name" : "",
"Ret" : 100,
"SessionID" : "0x00000006"
}Return code 100 indicates a successful operation.
List with some of the documented command, response and return codes can be found in DVRIP_Sofia_reference_codes Github repository.
For reassembling JSON objects, Wireshark has a JSON dissector built in:
-- Load JSON dissector
json = Dissector.get("json")
Dissected DVRIP message with JSON payload is shown below:

Media frames
The DVRIP/Sofia protocol has 5 distinct media frame types, each frame having unique header and a signature:
- I-Frame.
- P-Frame.
- Audio frame.
- Image frame.
- Information frame.
Schematics of DVRIP/Sofia media frame headers — taken from Xiongmai bitstream frame format document — are presented below:

Additional details about each frame type are presented below.
I-Frame
I-Frame header is 16 bits in length and has seven fields identified:

Each field can be described as follows:
- BIT 0–3: signature
- BIT 4: video codec (0x01 = MPEG4, 0x02 = H.264, 0x12 = H.265)
- BIT 5: encoded framerate (variable; 1–25 for PAL, 1–30 for NTSC)
- BIT 6: low 8 bits of image width; this value is actual width divided by 8
- BIT 7: low 8 bits of image height; this value is actual height divided by 8
- BIT 8–11: datetime of the capture
- BIT 12–15: length of I-Frame payload. Note: this can exceed the length of a single DVRIP/Sofia message, which has max length of 8192 bits.
Wireshark tree view of DVRIP/Sofia message with an I-Frame:

First 4 bits of an I-Frame payload (BITS 16–19) are equal to 0x00000001.
P-Frame
Header of a P-Frame is 8 bits long and has two identified fields:

- BIT 0–3: signature
- BIT 4–7: length of P-Frame payload
Wireshark tree view of DVRIP message with (start of) a P-Frame:

First 4 bits of a P-Frame payload (BITS 8–11) are equal to 0x00000001.
Audio Frame
Audio payload has 8 bit long header with four separate fields:

The fields can be explained as follows:
- BIT 0–3: signature
- BIT 4: audio codec (0x0e = G711A)
- BIT 5: sampling rate (0x02 = 8kHz sampling)
- BIT 6–7: length of audio payload
It gives the following view in Wireshark:

Information Frame
The information frame is 8 bits in length with four identified fields:

- BIT 0–3: signature
- BIT 4: general information (unconfirmed)
- BIT 5: unused value
- BIT 6–7: payload length
Used for information transmission. First byte after signature (byte 4):
- 0x01 — general information.
- 0x06 — unknown value.
Wireshark tree view of DVRIP message with E-Frame:

Additional Features
Another key addition to the dissector is the ability to reconstruct audio and video streams from a given .pcap file with collected streams being saved into /tmp directory. Each saved stream has it’s own key, which consists of:
- IP address of the camera.
- Video codec type (as indicated by Reserved byte №1 in DVRIP header).
- Audio codec type (as indicated by Reserved byte №2 in DVRIP header).
As mentioned before, the streams are saved to /tmp directory in Linux:

Stream Muxing
Combining separate audio and video streams into a single media file is not automated yet. However, for this purpose, the following ffmpeg command can be used from the CLI on Linux (Credits to kinsi55):
ffmpeg -i 192.168.1.103_0_0_video.h265 \
-f alaw \
-ar 8000 \
-ac 1 \
-i 192.168.1.103_0_128_audio.g711 \
-c:v copy \
-c:a mp3 \
-ar 44100 \
-f mp4 \
-y muxed.mp4
A single stream containing both audio and video is saved as muxed.mp4 :

DVRIP/Sofia Protocol Dissector for Wireshark (Written in Lua) was originally published in InfoSec Write-ups on Medium, where people are continuing the conversation by highlighting and responding to this story.
SOCIAL SHARE CARD GENERATOR