Reverse Engineer DDPAI Dash Cam Firmware

10 Oct 2019

I have been looking for a good mid range Dash cam for my car. I was thinking to integrate this to my custom build infotainment system. For this, there need to be some option to get live stream. I bought this DDPAI Mini Car Dash Camera from Amazon Link (Official site Link), but I was not sure whether I can access the live stream or not. The product looks good, companion app works smooth also, and there is live stream in the app. My goal was to figure out a way to get live stream so that I can use it in my program. May be have a look at it’s firmware and do some little hack if possible.

DDPAI

In the officail site, they have mentioned about Hi3516E. it is an SoC buid by HISILICON that runs on Linux-3.18 (probably running Huawei LiteOS Link). Looking into the datasheet Link, it supports following encodings,

  • H.264 BP/MP/HP
  • H.265 Main Profile
  • MJPEG/JPEG baseline

But from the official site, it’s mentioned H.264. So most probably the stream will be in this format. This came handy later in the process.

Once powered, camera starts a WiFi AP. Client can connect to this using default password 1234567890 and use companion app to configure. Put your WiFi interface to monitor mode and start listening on the traffic.

Initial traffic

I could see a lot of HTTP traffic between companion app and 193.168.0.1 which is the camera device. So they have some kind of REST API service running on port 80. Let’s look into the flow.

Flow

To get a sessionid they are calling POST /vcam/cmd.cgi?cmd=API_RequestSessionID. This set a Cookie and they are reusing it for further communication. Then they are synching the time. Why do they need imei number for that? Strange. let’s look the subsequent requests.

POST /vcam/cmd.cgi?cmd=API_GetBaseInfo HTTP/1.1
Accept-Encoding: gzip
sessionid: sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Cookie: SessionID=sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Content-Type: application/x-www-form-urlencoded
User-Agent: ****************************************
Host: 193.168.0.1
Connection: Keep-Alive
Content-Length: 0

HTTP/1.1 200 OK
Server: VYOU_HTTP_SERVER/2.1.3 CAM WEB 1.0
Content-Type: text/plain; charset=UTF-8
Date: Wed, 09 Oct 2019 21:54:41 GMT
Last-Modified: Wed, 09 Oct 2019 21:54:41 GMT
Connection: Keep-Alive
Cache-Control: no-cache,no-store
Content-Length: 651

{"errcode":0,"data":"{\"nickname\":\"vYou_DDPai_MINI\",\"password\":\"1234567890\",\"ordernum\":\"DDPaiMin\",\"model\":\"DDPai miniONE_LITE_Overseas\",\"version\":\"v4.7.0.22\",\"uuid\":\"*****-****-****-****-********\",\"sn\":\"\",\"macaddr\":\"**:**:**:**:**:**\",\"chipsn\":\"\",\"legalret\":1,\"btnver\":3,\"totalruntime\":7471,\"sdcapacity\":31158784,\"sdspare\":24202912,\"sdbrand\":\"\",\"hbbitrate\":10240,\"hsbitrate\":2048,\"mbbitrate\":10240,\"msbitrate\":2048,\"lbbitrate\":10240,\"lsbitrate\":2048,\"default_user\":\"A1000030EBAA94\",\"is_neeed_update\":0,\"edog_model\":\"\",\"edog_version\":\"\",\"edog_status\":2,\"cid\":\"\"}"}

GET /record.log HTTP/1.1
Accept-Encoding: 
User-Agent: ****************************************
Host: 193.168.0.1
Connection: Keep-Alive

HTTP/1.1 200 OK
Server: VYOU_HTTP_SERVER/2.1.3 CAM WEB 1.0
Content-Type: text/plain; charset=UTF-8
Date: Wed, 09 Oct 2019 21:54:41 GMT
Last-Modified: Wed, 09 Oct 2019 21:54:41 GMT
Connection: Keep-Alive
Cache-Control: no-cache,no-store
Content-Length: 4329

{"nickname":"vYou_DDPai_MINI","password":"1234567890","ordernum":"DDPaiMin","model":"","version":"v4.7.0.22","uuid":"***********************","sn":"","macaddr":"**:**:**:**:**:**","chipsn":"","legalret":1,"btnver":3,"totalruntime":7471,"sdcapacity":31158784,"sdspare":24202912,"sdbrand":"","hbbitrate":10240,"hsbitrate":2048,"mbbitrate":10240,"msbitrate":2048,"lbbitrate":10240,"lsbitrate":2048,"default_user":"A1000030EBAA94","is_neeed_update":0,"edog_model":"","edog_version":"","edog_status":2,"cid":""}
....

POST /vcam/cmd.cgi?cmd=APP_AvCapSet HTTP/1.1
Accept-Encoding: gzip
sessionid: sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Cookie: SessionID=sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Content-Type: application/x-www-form-urlencoded
User-Agent: ****************************************
Host: 193.168.0.1
Connection: Keep-Alive
Content-Length: 30

{"stream_type":0,"frmrate":30}

HTTP/1.1 200 OK
Server: VYOU_HTTP_SERVER/2.1.3 CAM WEB 1.0
Content-Type: text/plain; charset=UTF-8
Date: Wed, 09 Oct 2019 21:54:41 GMT
Last-Modified: Wed, 09 Oct 2019 21:54:41 GMT
Connection: Keep-Alive
Cache-Control: no-cache,no-store
Content-Length: 23

{"errcode":0,"data":""}

Some default user names, password, etc… It seems POST /vcam/cmd.cgi?cmd=APP_AvCapSet sets stream channel and fps.

POST /vcam/cmd.cgi?cmd=API_RequestCertificate HTTP/1.1
Accept-Encoding: gzip
sessionid: sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Cookie: SessionID=sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Content-Type: application/x-www-form-urlencoded
User-Agent: ****************************************
Host: 193.168.0.1
Connection: Keep-Alive
Content-Length: 70

{"user":"admin","password":"admin","level":0,"uid":"*************"}

HTTP/1.1 200 OK
Server: VYOU_HTTP_SERVER/2.1.3 CAM WEB 1.0
Content-Type: text/plain; charset=UTF-8
Date: Wed, 09 Oct 2019 21:54:41 GMT
Last-Modified: Wed, 09 Oct 2019 21:54:41 GMT
Connection: Keep-Alive
Cache-Control: no-cache,no-store
Content-Length: 23

{"errcode":0,"data":""}

POST /vcam/cmd.cgi?cmd=APP_AvCapReq HTTP/1.1
Accept-Encoding: gzip
sessionid: sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Cookie: SessionID=sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Content-Type: application/x-www-form-urlencoded
User-Agent: ****************************************
Host: 193.168.0.1
Connection: Keep-Alive
Content-Length: 0

HTTP/1.1 200 OK
Server: VYOU_HTTP_SERVER/2.1.3 CAM WEB 1.0
Content-Type: text/plain; charset=UTF-8
Date: Wed, 09 Oct 2019 21:54:41 GMT
Last-Modified: Wed, 09 Oct 2019 21:54:41 GMT
Connection: Keep-Alive
Cache-Control: no-cache,no-store
Content-Length: 173

{"errcode":0,"data":{"bs_pixel":"1920x1080","bs_bitrat":10240,"bs_frmrate":30,"ss_pixel":"854x480","ss_bitrat":1536,"ss_frmrate":30,"aud_samplerate":16000,"aud_pt":"AACLC"}}

POST /vcam/cmd.cgi?cmd=APP_AvCapReq This request put some light into the video stream details.

POST /vcam/cmd.cgi?cmd=APP_PlaybackLiveSwitch HTTP/1.1
Accept-Encoding: gzip
sessionid: sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Cookie: SessionID=sjqPqaCL9GPT8WanzOn1THq4T1iSKLm
Content-Type: application/x-www-form-urlencoded
User-Agent: ****************************************
Host: 193.168.0.1
Connection: Keep-Alive
Content-Length: 31

{"switch":"live","playtime":""}

This seems to be the one that triggers the live streams. Just after this request I could see a new TCP connection got established on port 6200.

stream tcp

This might be the port for streaming video. I could see a lot of traffic on this connection just after this.

stream tcp

Let’s see which all ports are opened in the cam. A quick scan using nmap shows following ports.

stream tcp

Port 443 is opened. But it is not a web/REST server like port 80. Sending curl http://193.168.0.1:443 doesn’t return anything. No respone from server. telnet 193.168.0.1 443 also had no luck. Might be running some other service. May be for flashing firmware.

Port 553 is running an rtsp service. This looks promising. Rtsp is for streaming video. Tried several common rtsp urls using VLC. All are returning BAD_REQUEST. No luck there. Port 6100 looks promissing. Let’s try opening this using VLC.

wget "http://193.168.0.1:6200" -qO- | cvlc -

Vlc is unable to show any streams. Let’s try to specify the stream details. May be Vlc was unable to figure out proper streaming details.

wget "http://193.168.0.1:6200" -qO- | cvlc - :demux=h264 --h264-fps=30 :clock-jitter=0

Finally!!! Vlc is able to get the stream with no issues. Smile… :-)

Now this is done let’s see whether I can access this from python & opencv.

import numpy as np
import cv2

cap = cv2.VideoCapture('tcp://193.168.0.1:6200/')
cap.set(5, 30)

while(True):
    # Capture frame-by-frame
    ret, frame = cap.read()

    # Our operations on the frame come here
    #gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # Display the resulting frame
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

Perfect. I was able to get the stream from python. Mission accomplished. But let’s look further.

I ran nmap one more time covering all the ports. It’s extensive and took some time. It showed one more port 6100. Later found to be audio stream from the camera.

Let’s check whether we can have a look into the firmware. I could not find any links online to download firmware. When I first started the companion app, it asked for firmware update. So there should be some way to get it. Let’s look into the companion app traffic.

Download some packet capture software for Android like ‘Packet Capture’. It’s similar to wireshark, but for Android. It seems comapnion app is communicating with server in plain http. No SSL certificate for the server. That’s not good. They are sending some personal details back to the server which is not good. Let’s look into the firmware update details.

Clicking on check firmware option triggers this request.

stream tcp

It seems they are sending both version details of firmware and companion app. Server seems to only respond with companion app details. This might probably because I am having latest firmware. Lets modify this request so that server think I am having a previous version.

curl -v -H 'Content-Type: application/json' -X POST http://apphw.ddpai.com/d/api/v1/version/check --data '{
  "data": [
    {
      "model": "camera_app_android_overseas",
      "version": "v5.7.12.0926"
    },
    {
      "model": "DDPai miniONE_LITE_Overseas",
      "version": "v4.6.0.22"
    }
  ]
}'
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 47.89.133.142...
* TCP_NODELAY set
* Connected to apphw.ddpai.com (47.89.133.142) port 80 (#0)
> POST /d/api/v1/version/check HTTP/1.1
> Host: apphw.ddpai.com
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 197
> 
* upload completely sent off: 197 out of 197 bytes
< HTTP/1.1 200 OK
< Date: Thu, 10 Oct 2019 17:33:54 GMT
< Content-Type: application/json;charset=UTF-8
< Content-Length: 1457
< Connection: keep-alive
< 
{
    "data": [
        {
            "bigMd5": "63D8900C41169ED76A107140F3119433",
            "bigPath": "http://httpsdatacdn.ddpai.com/update/camera_app_android_overseas/20180620/C05_ddpai_v5.7.9.0613_release_0613-1439_zip.apk",
            "bigSize": 42322062,
            "bigSmallSplit": "",
            "bigTime": 0,
            "commitDate": 1529486660540,
            "desc": "1、适配mix3机型;\n2、使用新的视觉风格;\n3、修复已知问题;",
            "descCn": "1、适配mix3机型;\n2、使用新的视觉风格;\n3、修复已知问题;",
            "descDe": "",
            "descEn": "1、Adopted to mix3;\n2、Adopted a new VI style;\n3、Fixed bugs;",
            "descEs": "",
            "descFr": "",
            "descIt": "",
            "descPt": "",
            "descRu": "",
            "descTw": "",
            "hasDel": false,
            "id": 64,
            "model": "camera_app_android_overseas",
            "name": "camera_app_android_overseas",
            "smallMd5": "",
            "smallPath": "",
            "smallSize": 0,
            "smallTime": 0,
            "smallVersion": "",
            "version": "v5.7.9.0613",
            "versionType": 1
        },
        {
            "bigMd5": "768A1E03AF163ACC53C0841AE5F5EF8B",
            "bigPath": "http://httpsdatacdn.ddpai.com/update/DDPaiminiONE_LITE_Overseas/20190614/update_nolog.tar.gz",
            "bigSize": 5365231,
            "bigSmallSplit": "",
            "bigTime": 60,
            "commitDate": 1560481333328,
            "desc": "修复已知问题;",
            "descCn": "修复已知问题;",
            "descDe": "",
            "descEn": "Fixed bugs;",
            "descEs": "",
            "descFr": "",
            "descIt": "",
            "descPt": "",
            "descRu": "",
            "descTw": "",
            "hasDel": false,
            "id": 74,
            "model": "DDPai miniONE_LITE_Overseas",
            "name": "DDPai miniONE_LITE_Overseas",
            "smallMd5": "",
            "smallPath": "",
            "smallSize": 0,
            "smallTime": 0,
            "smallVersion": "",
            "version": "v4.7.0.22",
            "versionType": 1
        }
    ],
    "error_code": 0
}
>

Ta daa!!! Our new firmware ready to be downloaded here http://httpsdatacdn.ddpai.com/update/DDPaiminiONE_LITE_Overseas/20190614/update_nolog.tar.gz. This is good. Let’s try to analyze the firmware.

bin walk

Eventhough it’s ramed as .gz, it doesn’t look a gzip compression. Running binwalk reveals that they use zlib compression. Let’s try to extract each part.

bin_walk_extract

Let’s see what each part is.

bin_walk_bin

We have two JFFS2 file system. Let’s see what’s inside. To extract JFFS2 file system you need to install jefferson.

jefferson

git clone https://github.com/sviehb/jefferson.git
Cloning into 'jefferson'...
remote: Enumerating objects: 74, done.
remote: Total 74 (delta 0), reused 0 (delta 0), pack-reused 74
Unpacking objects: 100% (74/74), done.
>ls
1CD  1CD.zlib  21458  21458.zlib  35DE85  35DE85.zlib  50AB7A  50AB7A.zlib  56  56.zlib  E3  E3.zlib  jefferson
>cd jefferson/
>ls
LICENSE  README.md  setup.py  src
>sudo python setup.py install
running install
running build
running build_py
creating build
creating build/lib.linux-x86_64-2.7
creating build/lib.linux-x86_64-2.7/jefferson
copying src/jefferson/rtime.py -> build/lib.linux-x86_64-2.7/jefferson
copying src/jefferson/jffs2_lzma.py -> build/lib.linux-x86_64-2.7/jefferson
copying src/jefferson/__init__.py -> build/lib.linux-x86_64-2.7/jefferson
running build_scripts
creating build/scripts-2.7
copying and adjusting src/scripts/jefferson -> build/scripts-2.7
changing mode of build/scripts-2.7/jefferson from 644 to 755
running install_lib
creating /usr/local/lib/python2.7/dist-packages/jefferson
copying build/lib.linux-x86_64-2.7/jefferson/rtime.py -> /usr/local/lib/python2.7/dist-packages/jefferson
copying build/lib.linux-x86_64-2.7/jefferson/jffs2_lzma.py -> /usr/local/lib/python2.7/dist-packages/jefferson
copying build/lib.linux-x86_64-2.7/jefferson/__init__.py -> /usr/local/lib/python2.7/dist-packages/jefferson
byte-compiling /usr/local/lib/python2.7/dist-packages/jefferson/rtime.py to rtime.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/jefferson/jffs2_lzma.py to jffs2_lzma.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/jefferson/__init__.py to __init__.pyc
running install_scripts
copying build/scripts-2.7/jefferson -> /usr/local/bin
changing mode of /usr/local/bin/jefferson to 755
running install_egg_info
Writing /usr/local/lib/python2.7/dist-packages/jefferson-0.2.egg-info
>cd ../
>ls
1CD  1CD.zlib  21458  21458.zlib  35DE85  35DE85.zlib  50AB7A  50AB7A.zlib  56  56.zlib  E3  E3.zlib  jefferson
>
>
>
>binwalk -e 35DE85

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JFFS2 filesystem, little endian

Let’s look into the file system.

tree_1

Under aud_res there are several raw audio files are present. These are the notofication sounds, produced by the dashcam. Replacing these with custom sounds and reflashing the firmware will be cool.

audacity

There is some libaiengine-80-liteos-2.9.4-20180402220227.so file. The filename liteos confirms it is running Huawei LiteOS.

There is one bin file called paramdef.bin located in paramdef. Seems to be binary file. Let’s see the strings in it.

>strings paramdef.bin 
arap
12.0
720P30
1080P30
1080P60
1440P30
1520P30
/app/sd/DCIM
/usrcfg/lastfilename.txt
/app/sd
/dev/mmcblk0p0
3.18.y
v4.7.0.22
DDPai miniONE_LITE
vYou_DDPai_MINI
1234567890
000000000000000
000000000000000
admin
admin
yyyy-MM-dd HH:mm:ss
zh_CN
DDPaiMin
v4.5.0.0
DDPai miniONE
2018-01-01
mrap

You could see WiFi SSID and default password, Firmware version, etc

Let’s see the other file system.

tree_2

This seems mostly configuration files. config_product_devmng.ini have details regarding WiFi SSID & passwords.

[devinfo]
system_version     = "3.18.y"
software_version   = "v4.7.0.22"
model              = "DDPai miniONE_LITE"

[wifi]
ssid               = "vYou_DDPai_MINI"
password           = "1234567890"
channel            = "11"
enable             = "1" 1:true 0:false
stawifissid        = "0"
stawifipwd         = "0"
stawifiauth        = "4"; 0:OPEN 1:WEP 2:WPAPSK 3:WPA2PSK 4:WPAPSK_WPA2PSK_MIX

[autooff]
enable             = "0"
time               = "300"; unit s

[dormant]
enable             = "0"
time               = "60"; unit s

[clock]
enable             = "0"

[onoff]
auto_poweron_en    = "0"; 0:disable, 1:enable
auto_poweron_time  = "600"; seconds
sd_card_cid        = "0000000000000000"
sd_card_csd        = "0000000000000000"
abnormal_poweroff  = "1"; 
onoff_key_press_time = "2000"; 200ms~2000ms

[sound]
keysound           = "1"
bootmusic          = "0"; 0: open bootmusic 1: close bootmusic

[gsensor]
gsensorrange       = "0"

[banma]
verification	   = "0"

There is some stawifissid params. May be enabling this will make Dashcam connect to a particular WiFi than creating an AP. Could see some reference for mounting at /app/sd/DCIM. Some of the config files have the details required for the live stream.

There is one more to extract.

bin_walk_last

This seems to be the main binary file resposible for the REST interface. Running strings list a lot of useful details. It seems to be written in c since I could see some reference here and there.

bin_walk_c

By just looking into strings, you will get a lot of information. I could able to extract the list of all APIs supported by the REST service.

  • API_RequestSessionID
  • API_RequestCertificate
  • API_SyncDate
  • API_GetBaseInfo
  • APP_AvCapReq
  • APP_AvCapSet
  • APP_PlaybackLiveSwitch
  • API_GetMailboxData
  • API_Logout
  • API_TestGsensor
  • API_TestSerial
  • API_TestIndicator
  • APP_PlaybackListReq
  • APP_PlaybackPageListReq
  • API_GeneralSave
  • API_GeneralQuery
  • APP_DeleteEvent
  • API_CameraCapture
  • API_AuthModify
  • API_GetStorageInfo
  • API_MmcFormat
  • API_GetModuleState
  • API_SetTimeForUpdateOrderNum
  • API_ClearModuleFlags
  • APP_AvInit
  • APP_EventListReq
  • APP_TimeLapseVideoListReq
  • API_PlayModeQuery
  • API_AuthQuery
  • API_Reboot
  • API_RestartWifi
  • API_UpdFileMd5
  • API_SetLogonInfo
  • API_GetLogonRecord
  • APP_ParkingEventListReq
  • APP_ParkingEventListClear
  • API_SuperDownload
  • API_ButtonMatch
  • API_WpsConnect
  • API_GetResolution
  • API_SetLockFile
  • APP_StopDownload
  • API_SetRouterAuth
  • API_GetRouterStatus
  • API_UpdateCamera
  • API_GetLegalInfo
  • API_GetSdBadClus
  • API_SetDefaultCfg
  • API_SetUuid
  • API_SetSn
  • API_GetEachFileSize
  • API_BanMaUnbind
  • API_BanMaSync
  • API_SetApMode
  • APP_EquipTestReady
  • API_HwinfoQuery
  • API_EquipAudioLoop
  • API_EquipGetTime
  • API_EquipLED
  • API_EquipButtonMatch
  • API_SetTestResult
  • API_EquipGSensor
  • API_EquipSpeaker
  • API_EquipResetBtn
  • API_EquipPhotoBtn
  • API_EquipMuteBtn
  • API_EquipMuteBtn
  • API_EquipResetCfg
  • API_EquipLegalSet
  • API_EquipGetSensorVer
  • API_EquipOpenRtsp
  • API_RecordOpt
  • API_GetGsensorState
  • API_EquipACCState
  • API_EquipGetTempetureAndHumidity
  • API_EquipGetWiFiStatus
  • API_EquipDeleteFacUsbFile
  • API_EquipODBTest
  • API_SetGsensorValue
  • API_SetAudioCapGain
  • API_SetBitRate1
  • API_SetBitRate2
  • API_SetBitRate3
  • API_SetBitRate4
  • API_SetBitRate5
  • API_SetBitRate6
  • API_SetBitRate7
  • API_SetBitRate8
  • API_SetBitRate9
  • API_SetBitRate10
  • API_SetBitRate11
  • API_SetBitRate12
  • API_SetBitRate13
  • API_SetBitRate14
  • API_SetBitRate15
  • API_SetBitRate16
  • API_SetBitRate17
  • API_SetBitRate18
  • API_GetAispeechState
  • API_SetSpeakRange
  • API_SetEmmcMeasure
  • API_GetEmmcMeasure
  • API_Get_ConnectAccStatus
  • API_Get_PageFileListStatus
  • API_SetTarCamlog
  • API_GetCarCustomVersion
  • API_SetPowerOff

This is fun… !!!