ILDVR INC-MH40D06 security nightmare part 2

I have put off the ILDVR camera, as I kind of lost interest.
For previous posts see here, here and here.

I was bored so I decided to poke at again.

I was interested where does the camera store users and in what format. What I found out is an atrocious mocking of security.
The camera stores local users and their passwords (in plain text) in following file:

/mnt/flash/data/OwnUserInfo.txt

Yep: the same directory which is accessible without auth via port 10081. So if you forgot password (and forgot the silly hardcoded HANKVISION), then you can get a reminder what it is by simply going here:

http://${CAMERA_IP}:10081/OwnUserInfo.txt

There is also another “binary” file that contains interesting references to HANKVISION and local users:

/mnt/flash/data/UserInfo

strings that and you get following:

HANKVISION
e82f5af1f39f021b44e78089b5a40a8e0aa8d2768c705e8f139bec04d87d5a54
8f081b5a8e0685ca975a01d4159930f9
0d9a1f80bcc7a1e4a00f04588062ed67
admin
8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
21232f297a57a5a743894a0e4a801fc3
76eb00c6458e9b2755b570ae565ba0a6

Changing the password to HANKVISION reveals that this string is “encoded” “HANKVISION”:

e82f5af1f39f021b44e78089b5a40a8e0aa8d2768c705e8f139bec04d87d5a54
8f081b5a8e0685ca975a01d4159930f9

Not sure if the obfuscation is worth spending time on, especially when we already know HANKVISION is hardcoded in web server binary and OwnUserInfo.txt already contains passwords in clear texts.

strace-ing ‘webs’ process during certain conditions opens the /tmp/umconfig.txt, which contains following:

TABLE=users
ROW=0
name=HANKVISION
password=0d9a1f80bcc7a1e4a00f04588062ed67
group=Administrator
prot=1
disable=0
ROW=1
name=admin
password=76eb00c6458e9b2755b570ae565ba0a6
group=Administrator
prot=1
disable=0
ROW=2
name=adminadmin
password=5ca1e16e4fa3fa58b6656b9ad547fa0f
group=Normal
prot=0
disable=0
TABLE=groups
ROW=0
name=Administrator
priv=4
method=2
prot=0
disable=0
ROW=1
name=Normal
priv=4
method=2
prot=0
disable=0
TABLE=access
ROW=0
name=/browse/
method=2
secure=0
ROW=1
name=/jpgimage/
method=2
secure=0
ROW=2
name=/mjpgstreamreq/
method=2
secure=0
ROW=3
name=/form/
method=2
secure=0
group=Administrator
ROW=4
name=/cgi/
method=2
secure=0

The “hashes” correlate to /mnt/flash/data/UserInfo…

Looking firmware upload function (in browse/javascript/sysInf.js) I found this bit:

function fileUpload(){
...
		var typeAllow = [".ifu", "macaddr.txt", "deviceid.txt", "sn.txt", "audio.dat", ".bin", ".png", ".ifc", ".lib", ".uid", ".pid","logo.gif","whitelist.txt"];
		var fileType = ["ifu", "mac", "deviceid", "sn", "audio", "bin", "png", "ifc", "lib", "uid", "pid","gif","wlst"];
....

I have tested the upload function with logo.gif and that worked: the logo on top got replaced, so it brings a possibility of doing something more (sneaking in a binary?).

Looking at ‘webs’ binary I decided to google for strings in case someone leaked the source or these bastards stole somebody else’s work.
Here what I found:
The string:

webs: websWrite lost data, buffer overflow

Matches suspiciously named file here:
https://github.com/socoola/yhrouter/blob/master/user/goahead/src/webs.c

Same could be said for these strings:

webs: Listening for HTTP requests at address %s
webs: accept request

What is surprising is that they avoided doing execve calls where they could. IP addresses, routes, all set via ioctl, even time is set via settimeofday function. This removed possibility of command injection.

Here is what I believe is going on with this firmware:

The video side and core functionality has been lifted off SDK by Hisilicon. The web server stuff has been implemented by actual Hankvision people, most likely low paid undergraduate Chinese students. The core web server functionality has been lifted off the internet (see above).

What could have been done better without spending much on development:

Remove hard coded passwords!
Throw away all activeX crap (use MJPEG stream for “preview”).
Turn off telnet and leave ssh on with configurable password (perhaps make it a separate user?).
Do not store plain passwrods anywhere
Throw away all the dyndns and cloud nonsense.
Add actual off checkbox for FTP, Mail and SIP stuff (and possbly throw away SIP stuff).
Add VLC plug-in functionality.
Remove web server that listens on port 10081 exposing whole bunch of private data.

I am not sure what they are trying to achieve by not allowing SSH/Telnet access, but this is counter productive. I will not buy a security product to which I do not have control! Besides if I wanted to get access to your firmware, I don’t need SSH or Telnet, when I have RS232 and soldering iron.

For those who purchased this camera, if you really have to use it do the following:

Hexedit webs binary and change the HANKVISION bit to something else

And

Remove gateway setting (set it the same IP as camera) and preferably isolate camera from rest of the network (separate VLAN and port forwarding to recorder).

Or

Just chuck it in the bin and never purchase anything from ILDVR again.

Shame on you ILDVR for not responding to me when I contacted you almost a year ago about hard coded passwords. Shame on you ILDVR for not providing root password or firmware updates.

python OpenCV basic motion detection

Here I will describe how I use OpenCV for capturing RTSP streams, with purpose of motion detection.

For basic OpenCV I use these two libraries:

import cv2
import numpy as np

cv2 is OpenCV library (second version), and numpy is python numeric lybrary (useful for manipulating matrices among other things).

To initiate capture one simply does following:

cap = cv2.VideoCapture('rtsp://192.168.1.69:554/Streaming/Channels/2')

In this example I use second stream (of lower resolution) for motion detection.

From there you can get heigh and width of the frame (this will be useful later):

width = cap.get(3)
height = cap.get(4)

I use BackgroundSubtractorMOG for motion detection (somewhat cheating ;)):

bg = cv2.BackgroundSubtractorMOG(100,3,0.6,30)

The magic is in parameters, I used following:
100 – history
3 – number of Gaussian mixtures
0.6 – background ratio
30 – noise strength
The numbers above are not necessarily “correct” but I came to them with error and trial (and “guestimation”).
Here is document in detail describing this algorithm: http://personal.ee.surrey.ac.uk/Personal/R.Bowden/publications/avbs01/avbs01.pdf

The actual capture loop looks something like this:

while(True):
    ...
    ret, frame = cap.read()
    motion = bg.apply(frame, learningRate=0.005)
    kernel = np.ones((3, 3), np.uint8)
    motion = cv2.morphologyEx(motion, cv2.MORPH_CLOSE, kernel, iterations=1)
    motion = cv2.morphologyEx(motion, cv2.MORPH_OPEN, kernel, iterations=1)
    motion = cv2.dilate(motion,kernel,iterations = 1)
    contours, hierarchy = cv2.findContours(motion, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    ...
ret, frame = cap.read()

captures a single frame

motion = bg.apply(frame, learningRate=0.005)

extracts a black and white image with the background removed (learnignRate value has been chosed by error and trial).

Next four lines simply manipulate extracted image in such that it does following:
MORPH_CLOSE: removes small holes (up to 3×3 pixel, defined by kernel) within the object (“white”) in the extracted motion matrix.
MORPH_OPEN: removes small dots within the “background” (black) in the extracted motion matrix.
dilate: is making sure there all adjacent islands are joined together, so when we extract contours we get small amount of contours as result.

The “3×3 pixel” block comes from here:

kernel = np.ones((3, 3), np.uint8

The last step from processing frame is extracting the contours:

contours, hierarchy = cv2.findContours(motion, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

The extracted contours can then be iterated and hull drawn around them:

for cnt in contours:
    hull = cv2.convexHull(cnt)

The hull points then can be checked against the mask if motion is inside of the area of interest:

Lets define the mask as the whole frame (I am pretty sure there is a better way;)):

mask_points = [
( 0 , 0 ),
( 1 , 0 ),
( 1 , 1 ),
( 0 , 1 ),
]

mask_array=[]
for point in mask_points:
    mask_array.append([[int(point[0] * width ), int(point[1] * height )]])
mask = np.array(mask_array, np.int32)

This looks cumbersome, but what I am achieving here is converting mask_points list of human readable relative coordinate tuples (eg: centre will be at (0.5,0.5)). Mask can be defined as a polygon with relative positioning of each corner to the frame (independent from pixel size).

We check if hull point is inside our mask

for point in hull:
    distance = cv2.pointPolygonTest(mask,tuple(point[0]),1)
    if distance > 0:
        it_is_inside()

and vice-versa (in case if mask is smaller than the frame):

Note: The colour is defined by this tuple: (0,255,0)

Then do display the whole thing insert this inside of the while(True):

cv2.imshow('motion',frame)
k = cv2.waitKey(30) & 0xff
if k == 27:
    break

The above is basic idea behind my motion detection scripts. I have omitted a lot of glue logic and arithmetic due to my script is not ready for public display ;).

Prezzy card scam

A while ago I was gifted a prezzy card. These buggers: https://www.prezzycard.co.nz/.
They are actually Kiwi bank.
This prezzy card is not accepted in many places. In addition it needs to be activated.
I was putting it off, until the bloody thing expired.

So these bastards take legal tender (that does not have expiry) and set expiry on it.
I simply see no point of these things as cash is accepted pretty much everywhere, and it can also be deposited in your account (stating obvious here).

What these gift card/prepay debit cards companies rely on is chumps like me that let their cards expire.
This is a very shady business model.

So if you want to be a good friend and want to gift some cash, just gift the cash. No prezzy card or Dicksmith gift cards or any other stupid vouchers, thank you very much.

As for Kiwibank – shame on you for supporting such scam.

Extracting GPS data from Viofo A119 and other Novatek powered cameras

The script.

Here it is: nvtk_mp42gpx.py

What does it do?

This script will attempt to extract GPS data from Novatek MP4 file and output it in GPX format.

Usage: ./nvtk_mp42gpx.py -i<inputfile> -o<outfile> [-f]
        -i input file (will quit if does not exist)
        -o output file (will quit if exists unless overriden)
        -f force (optional, will overwrite output file)

In short: it takes Novatek encoded MP4 file (with embedded GPS data) and extract GPS data in GPX format (as separate file). Note; it does not modify the original MP4 file.

In long:
Continue reading

mini0806 design flaw

Alternative title: why my mini0806 was crashing and stopped working after a light drop.

One of the differences between mini0805 and mini0806 is this heat sink:
IMG_20160703_110658

It is a bitch to remove, so I gently coerced it with a blade:
IMG_20160703_110803

After some time of gentle pushing and heating it managed to peel off:
IMG_20160703_111536

Revealing Samsung ram chip and Ambarella A7LA50 SoC:
IMG_20160703_111758

Here is why it was not working after slight drop (and crashing before that):
IMG_20160703_113213

Pulled out pads! Note that the blade I used to remove would cause other side of pads missing if I applied too much pressure.

My theory is following: the heat sink that bridges RAM and SoC is causing stress on these chips. Mostly because it is glued and not mechanically pressed against the chips. This is a very silly design flaw, if for example, only SoC would have the heat sink (like found on IP cameras) then there would not be any stresses. I don’t think RAM needs heat sinking on those things.

I am tempted to take dremel to my other mini and split the heat sink in two…