{"id":1975,"date":"2022-04-21T13:16:22","date_gmt":"2022-04-21T11:16:22","guid":{"rendered":"https:\/\/www.dpin.de\/nf\/?p=1975"},"modified":"2022-04-21T14:36:13","modified_gmt":"2022-04-21T12:36:13","slug":"cheap-surveillance-cameras-nvr-hacking-to-work","status":"publish","type":"post","link":"https:\/\/www.dpin.de\/nf\/cheap-surveillance-cameras-nvr-hacking-to-work\/","title":{"rendered":"Cheap Surveillance Cameras &amp; NVR &#8211; Hacking to work"},"content":{"rendered":"<p>Recently I needed some video surveillance camera setup for our small office. There will be some de-and then construction work in our area and I just want to make sure that this does not attract some morons&#8230; anyway.<\/p>\n<p>So I looked around and since this will definitely not be a permanent thing (I despise video surveillance!) I did not want to invest a lot of money. I also did not want some cheap cameras from anywhere connected to my LAN so that anyone getting access to the camera eventually also getting access to our LAN.<\/p>\n<p>Then on eBay I found what seemed to fit my bill and be pretty cheap at the same time, a Chinese set comprised of four WiFi\/LAN cameras plus a central control and recording unit (NVR) from a maker called SANNCE. The set is also called &#8222;5322-W&#8220;, the NVR has the model number &#8222;N48WHE&#8220; and the cameras are &#8222;I71GL&#8220;.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-1981\" src=\"https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-1024x1024.jpg\" alt=\"\" width=\"625\" height=\"625\" srcset=\"https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-1024x1024.jpg 1024w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-300x300.jpg 300w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-150x150.jpg 150w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-768x767.jpg 768w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-624x624.jpg 624w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-176x176.jpg 176w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit-60x60.jpg 60w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/sannce-nvr-4kit.jpg 1501w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/p>\n<p>I had heard a bit about such dedicated surveillance cameras and expected the cameras to have a more or less dysfunctional web interface and that I would likely be able to stream data from them by hacking up some curl\/wget\/rtsp stuff on our server and be done with it.<\/p>\n<p>But not with this set, I had to find out.<\/p>\n<h3>Getting Inside<\/h3>\n<p>Don&#8217;t get me wrong, the cameras are pretty good, good picture, good night vision, pretty OK lenses (pretty wide angle), the NVR unit does what you expect from such a thing, i.e. displaying all camera feeds at the same time on an attached VGA or HDMI screen &#8211; all fine and dandy!<\/p>\n<p>But I could not get a single frame out of this setup from a Linux machine! The web interface of the NVR box itself requires a &#8211; lo and behold &#8211; Shockwave Flash plugin! What on earth!? But at least it did expose the network and authentication credentials for the WiFi, that the NVR spans up for the cameras. So I attached my laptop to that WiFi and looked at the cameras &#8211; no web interface either! And they also did not react to the most common camera interfaces, be it RTSP or HTTP. Super annoying.<\/p>\n<p>So I opened up the NVR device. To my (not so) great surprise I found a tiny PCB in there with a tiny chip in the middle with a heat sink, the WiFi module and a mystery radio module I have no idea what it is good for. But there was also a 2.54mm pitch four position pin header! And even silkscreen print on the PCB that said &#8222;GND RX0 TX0 NULL&#8220; &#8211; ha! If this isn&#8217;t a serial interface! In the picture below you can see I have attached three wires to the pinheader, black, blue and yellow, towards the bottom right corner, next to the screw:<\/p>\n<div id=\"attachment_1979\" style=\"width: 635px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-1979\" class=\"wp-image-1979 size-large\" src=\"https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/nvr-pcb-2048-1024x700.jpg\" alt=\"\" width=\"625\" height=\"427\" srcset=\"https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/nvr-pcb-2048-1024x700.jpg 1024w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/nvr-pcb-2048-300x205.jpg 300w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/nvr-pcb-2048-768x525.jpg 768w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/nvr-pcb-2048-1536x1050.jpg 1536w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/nvr-pcb-2048-624x427.jpg 624w, https:\/\/www.dpin.de\/nf\/wp-content\/uploads\/sites\/2\/2022\/04\/nvr-pcb-2048.jpg 2048w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><p id=\"caption-attachment-1979\" class=\"wp-caption-text\">NVR PCB<\/p><\/div>\n<p>As usual with these embedded devices, this is not a RS232, so the level shifters are missing which means you will need something like an FTDI or other UART adapter with not more than 5V output. And then, here you go, this is the boot up:<\/p>\n<pre>IPL g8e04b60\r\nD-1f\r\nHW Reset\r\nmiupll_233MHz\r\nMIU0 zq=0x003d\r\nmiu_bw_set\r\nutmi_1_init done\r\nutmi_2_init done\r\nutmi_3_init done\r\nusbpll init done......\r\nSPI 54M\r\nclk_init done \r\nP1 USB_rterm trim=0x0001\r\nP1 USB_HS_TX_CURRENT trim=0x0001\r\nP2 USB_rterm trim=0x0001\r\nP2 USB_HS_TX_CURRENT trim=0x0002\r\nP3 USB_rterm trim=0x0001\r\nP3 USB_HS_TX_CURRENT trim=0x0002\r\nPM_vol_bgap trim=0x0004\r\nGCR_SAR_DATA trim=0x0192\r\nETH 10T output swing trim=0x0000\r\nETH 100T output swing trim=0x0000\r\nETH RX input impedance trim=0x0000\r\nETH TX output impedance trim=0x0001\r\nMIPI_HS_RTERM trim=0x0001\r\nMIPI_LP_RTERM trim=0x0000\r\nTX_current trimming trim[0x1A]=0x0041\r\nTX_current trimming trim[0x1B]=0x0043\r\nTX_current trimming trim[0x1C]=0x0040\r\nHDMI2TX Rterm trim=0x0001\r\nHDMI2TX_Ibias_CH0 trim=0x001a\r\nHDMI2TX_Ibias_CH1 trim=0x001b\r\nHDMI2TX_Ibias_CH2 trim=0x001b\r\nHDMI2TX_Ibias_CH3 trim=0x001b\r\n256MB\r\nBIST0_0001-OK\r\nEnable MMU and CACHE\r\nLoad IPL_CUST from NOR\r\noffset:00010000\r\nUnable to detect NOR\r\nLoad time 683 us, 25862 KiB\/s\r\ncrc OK\r\n\r\nIPL_CUST g8e04b60\r\nMXP found at 0x00020000\r\nrunUBOOT()\r\nrunUBOOT()\r\n[SPI_NOR]\r\n-Verify UBOOT CRC32 passed!\r\n-Decompress UBOOT XZ\r\ndecomp_size=0x00081788\r\nDisable MMU and D-cache before jump to UBOOT\ufffd\r\n\r\nU-Boot 2015.01 (Aug 12 2020 - 10:21:49)\r\n\r\nVersion: I2g45a386a\r\nI2C: ready\r\nDRAM: \r\nWARNING: Caches not enabled\r\ngpio debug MHal_GPIO_Pad_Set: pin=44\r\ngpio debug MHal_GPIO_Pad_Set: pin=17\r\ngpio debug MHal_GPIO_Pad_Set: pin=18\r\ngpio debug MHal_GPIO_Pad_Set: pin=39\r\nnor_flash_mxp allocated success!!\r\nFlash is detected (0x1003, 0x20, 0x70, 0x18)\r\nSF: Detected nor0 with total size 16 MiB\r\nMXP found at mxp_offset[3]=0x00020000, size=0x1000\r\nenv_offset=0x60000 env_size=0x10000\r\nFlash is detected (0x1003, 0x20, 0x70, 0x18)\r\nSF: Detected nor0 with total size 16 MiB\r\nIn: serial\r\nOut: serial\r\nErr: serial\r\nNet: MAC Address 00:32:65:00:00:04\r\nAuto-Negotiation...\r\nLink Status Speed:100 Full-duplex:1\r\nsstar_emac\r\nBootLogo IN(234f8eb0 40000), OUT(f700000, 800000), DISP(1280 1024 60), Interface:1\r\n_BootLogoHdmitxCtrl 3258\r\nFlash is detected (0x1003, 0x20, 0x70, 0x18)\r\nSF: Detected nor0 with total size 16 MiB\r\nSF: 2621440 bytes @ 0x70000 Read: OK\r\n## Booting kernel from Legacy Image at 22000000 ...\r\nImage Name: MVX4##I2M#gc45adec26KL_LX409##[B\r\nImage Type: ARM Linux Kernel Image (lzma compressed)\r\nData Size: 2613036 Bytes = 2.5 MiB\r\nLoad Address: 20008000\r\nEntry Point: 20008000\r\nVerifying Checksum ... OK\r\n-usb_stop(USB_PORT0)\r\n-usb_stop(USB_PORT1)\r\n-usb_stop(USB_PORT2)\r\nUncompressing Kernel Image ... \r\n[XZ] !!!reserved 0x21000000 length=0x 1000000 for xz!!\r\nXZ: uncompressed size=0x522000, ret=7\r\nOK\r\natags:0x20000000\r\n\r\nStarting kernel ...\r\n\r\nBooting Linux on physical CPU 0x0\r\nLinux version 4.9.84 (root@linux) (gcc version 4.9.4 (Buildroot 2017.08-gc7bbae9-dirty) ) #55 SMP PREEMPT Sat Nov 21 10:21:23 CST 2020\r\nCPU: ARMv7 Processor [410fc075] revision 5 (ARMv7), cr=50c5387d\r\nCPU: div instructions available: patching division code\r\nCPU: PIPT \/ VIPT nonaliasing data cache, VIPT aliasing instruction cache\r\nearly_atags_to_fdt() success\r\nOF: fdt:Machine model: INFINITY2M SSC010A-S01A-S\r\nLXmem is 0xff00000 PHYS_OFFSET is 0x20000000\r\nAdd mem start 0x20000000 size 0xff00000!!!!<\/pre>\n<p>This shows us two things:<\/p>\n<ol>\n<li>there is a serial console, bingo! (115200 8N1 BTW)<\/li>\n<li>there is UBoot booting the thing which subsequently starts:<\/li>\n<li>a Linux kernel and system!<\/li>\n<\/ol>\n<p>Ok, that was three things.<\/p>\n<h3>Intermission &#8211; Free Software<\/h3>\n<p>Let&#8217;s take a pause for a moment here. What we have learned so far is that this thing is full of open source and free software. And it&#8217;s not only the NVR that is, it&#8217;s also the cameras! UBoot, Linux, Busybox, the whole embedded Linux shebang. Offer for sourcecode anywhere? None. Do they, i.e. the manufacturer SANNCE even mention it anywhere? No. Would they have to? Hell, yes!<\/p>\n<p>So this is a pretty clear case of free software license infringement by a larger Chinese corporation making money, and I guess quite a bit, from illegally using free software. Very very annoying. And they can get away with it! Try to sue a Chinese company for releasing the code, good luck!<\/p>\n<p>This is very annoying, very. But well, can not do much about it now, so let&#8217;s continue trying to pry the software open so that we can make (more) use of it.<\/p>\n<h3>Getting Shell Access &#8211; Almost root<\/h3>\n<p>The system that gets booted is pretty closed. All boot files are in immutable filesystems like Squashfs. You can stop UBoot from booting through so I tried the usual trick like &#8222;init=\/bin\/sh&#8220; on the kernel commandline but that did not work. Also after booting up you do not get to a shell prompt on the serial console either, too bad. From the network side there are only port 80 and 10000 open. Port 10000 is likely the port for streaming data from the device but the protocol is unknown. Port 80 is the web interface of the NVR. So no shell access either.<\/p>\n<p>The I found this <a href=\"https:\/\/gist.github.com\/aSmig\/e50058a54ab85428915521f233ffa3d0\" target=\"_blank\" rel=\"noopener\">gist thread<\/a> on Github and this has a real pearl in it:<\/p>\n<pre>me@here:\/media\/me\/SANDISK$ echo 1000000001 &gt; enable_log_forever\r\nme@here:\/media\/me\/SANDISK$ cat &lt;&lt;EOF&gt;app.out\r\n#!\/bin\/sh\r\n\/usr\/sbin\/telnetd &amp;\r\necho 'toor::0:0:root:\/root:\/bin\/sh' &gt;&gt; \/etc\/passwd\r\nexec \/media\/usb1\/app.out_chain \"\\$@\"\r\nEOF\r\nme@here:\/media\/me\/SANDISK$ cat &lt;&lt;EOF&gt;app.out_chain\r\n#!\/bin\/sh\r\numount \/opt\/app\/app.out\r\nexec \/opt\/app\/app.out \"\\$@\"\r\nEOF\r\nme@here:\/media\/me\/SANDISK$<\/pre>\n<p>When doing this on a Linux workstation with a VFAT blank formatted USB stick, creating these three files enable_log_forever, app.out and app.out_chain, insert the stick into the NVR and power it up you will now also get a telnetd running on port 53! Problem is, how to log into that thing? On the internet you also find some default root passwords for devices like this, like &#8222;j1\/_7sxw&#8220; or &#8222;xmhdipc&#8220; &#8211; sorry, no root. Also the approach taken in the above quoted script trying to create a new user &#8222;toor&#8220; with empty password did not work for me either since a) the rootfs is read only, where also the \/etc\/passwd is stored on and b) my system seems to have been upgraded to shadow passwords.<\/p>\n<p>Can it be so complicated!?<\/p>\n<p>So I added something else to the scripts&#8230; knowing that the serial interface is ttyS0 I added this line to the app.out script:<\/p>\n<pre>( cat \/dev\/ttyS0 | sh - &gt; \/dev\/ttyS0 ) &amp;<\/pre>\n<p>This should gives us a really very bare metal root shell &#8211; and indeed it does! This way I was able to get the content of the passwd and shadow files and found that there is a user &#8222;stb&#8220; with the same password, i.e. &#8222;stb&#8220;, so finally we can log in:<\/p>\n<pre>Escape character is '^]'.\r\n(none) login: stb \r\nPassword: \r\nlogin: can't change directory to '\/home\/stb'\r\nWelcome to MiLinux.\r\n\/ $<\/pre>\n<p>But of course we are now not root, just a regular user. Nevertheless this is already a lot more comfortable than the hacky serial shell.<\/p>\n<p>The stb password was found within seconds using <a href=\"https:\/\/www.openwall.com\/john\/pro\/linux\/\" target=\"_blank\" rel=\"noopener\">John<\/a>, the root password seems to be a tougher nut to crack, my small workstation is still working on it.<\/p>\n<h3>Accessing The Video Streams<\/h3>\n<p>Getting access to the stream via the NVR seems a still unsolved problem. Some older models or firmware versions seem to have had RTSP enables and even the SANNCE support replied to use RTSP to get access but this very obviously does not work, i.e.:<\/p>\n<pre>rtsp:\/\/192.168.1.23:80\/ch0_0.264<\/pre>\n<p>They said that ch0 is the stream of camera 1, ch1 would be of camera 2 etc. and that _0 is the main stream and _1 the preview stream. But that does not work, it fails immediately since the NVR does not send any data and closes the connection instantly. So I am more focusing on the cameras now. Accessing the cameras on the WiFi directly is an option but that requires of course that the party trying to get access needs to attach to the camera WiFi network. But the NVR already has this and it is also connect to my local LAN via Ethernet. So why not use that as a router?<\/p>\n<p>This can be done pretty easily by enabling IP forwarding, it is Linux after all! And that&#8217;s the commandline for it:<\/p>\n<pre>sysctl -w net.ipv4.conf.all.forwarding=1<\/pre>\n<p>This actually works! I was able to enter this above commandline in my improvised serial console shell and tadah &#8211; I can add a route on my workstation using the NVR as gateway into the private camera WiFi network:<\/p>\n<pre>route add -net 172.20.14.0 netmask 255.255.255.0 gw 192.168.2.169<\/pre>\n<p>So the net 172.20.14.0 is the IPv4 network the NVR creates for the camera WiFi, the 192.168.2.169 is the IPv4 address that the NVR was assigned from my DHCP server. One of the cameras has the IP 172.20.14.39 and so I can now access this IP from my workstation:<\/p>\n<pre>$ ping 172.20.14.39\r\nPING 172.20.14.39 (172.20.14.39) 56(84) bytes of data.\r\nFrom 192.168.2.169: icmp_seq=1 Redirect Host(New nexthop: 172.20.14.39)\r\n64 bytes from 172.20.14.39: icmp_seq=1 ttl=63 time=4.19 ms\r\nFrom 192.168.2.169: icmp_seq=2 Redirect Host(New nexthop: 172.20.14.39)\r\n64 bytes from 172.20.14.39: icmp_seq=2 ttl=63 time=3.45 ms\r\nFrom 192.168.2.169: icmp_seq=3 Redirect Host(New nexthop: 172.20.14.39)\r\n64 bytes from 172.20.14.39: icmp_seq=3 ttl=63 time=3.06 ms\r\nFrom 192.168.2.169: icmp_seq=4 Redirect Host(New nexthop: 172.20.14.39)\r\n64 bytes from 172.20.14.39: icmp_seq=4 ttl=63 time=2.84 ms\r\nFrom 192.168.2.169: icmp_seq=5 Redirect Host(New nexthop: 172.20.14.39)\r\n64 bytes from 172.20.14.39: icmp_seq=5 ttl=63 time=4.23 ms<\/pre>\n<p>Why I do get these &#8217;nexthop&#8216; messages I am not sure yet, but they stop after a while. So now we have IP access to the camera WiFi net, great!<\/p>\n<p>The cameras themselves do not use any common way for getting video out of them, so no RTSP, ONVIF etc. But since I am also not the first person to play with these I found a bunch of hints for different models of similar cameras and some of these actually work!<\/p>\n<p>By now I can confirm that you can actually stream video from the cameras using HTTP, like here using ffplay, you need some additional magic headers for it to work:<\/p>\n<pre>ffplay -f:v hevc -headers \"Authorization: Basic YWRtaW46\" \"http:\/\/172.20.14.39:80\/livestream\/11?action=play&amp;media=video_audio_data\"<\/pre>\n<p>Which gives you the main stream of the camera. If you replace &#8222;&#8230;livestream\/11&#8230;&#8220; with &#8222;&#8230;\/12&#8230;&#8220; you will get the reduced preview stream instead. The &#8222;Authorization: Basic YWRtaW46&#8220; is not giving away anything here, it is just the base64 encoded string &#8222;admin:&#8220; which means username &#8218;admin&#8216; and no password.<\/p>\n<p>You can get a JPEG preview image too using wget:<\/p>\n<pre>wget --auth-no-challenge --user='admin' --password='' http:\/\/172.20.14.39:80\/snapshot.jpg<\/pre>\n<p>I do not know yet if also higher resolution snapshots can be taken, that would be nice (the default seems to be a JPEG of the second stream, in my case this is fixed to 640&#215;360).<\/p>\n<p>To be continued&#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently I needed some video surveillance camera setup for our small office. There will be some de-and then construction work in our area and I just want to make sure that this does not attract some morons&#8230; anyway. So I looked around and since this will definitely not be a permanent thing (I despise video surveillance!) I did not want&#8230; <a href=\"https:\/\/www.dpin.de\/nf\/cheap-surveillance-cameras-nvr-hacking-to-work\/\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":1981,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,2],"tags":[],"class_list":["post-1975","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devices","category-geek-stuff"],"_links":{"self":[{"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/posts\/1975","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/comments?post=1975"}],"version-history":[{"count":11,"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/posts\/1975\/revisions"}],"predecessor-version":[{"id":1988,"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/posts\/1975\/revisions\/1988"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/media\/1981"}],"wp:attachment":[{"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/media?parent=1975"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/categories?post=1975"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dpin.de\/nf\/wp-json\/wp\/v2\/tags?post=1975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}