Reverse Engineer DDPAI Dash Cam Firmware
10 Oct 2019I 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.
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.
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.
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.
This might be the port for streaming video. I could see a lot of traffic on this connection just after this.
Let’s see which all ports are opened in the cam. A quick scan using nmap shows following ports.
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.
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.
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.
Let’s see what each part is.
We have two JFFS2 file system. Let’s see what’s inside. To extract JFFS2 file system you need to install 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.
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.
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.
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.
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.
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… !!!