Server:Server Status

Improving the Motorola Blink Baby Monitor/Camera

So we recently purchased the Motorola Blink1 Wifi Baby Monitor (Is this the first blog post acknowledging the baby? He's due any day!) and it's neat hardware with SHIT software. Straight up, it doesn't do most things you would want. I'm working to reverse engineer this and make it workable...particularly on linux.

Before I start...MAD PROPS to Simon Aldrich, whose article laid the foundation to what I'm trying to do here. This got me started: Hacking the Motorola Blink 1 Baby Monitor (Part 1) and Hacking the Motorola Blink 1 Baby Monitor (Part 2)

So, here's the goals for this product that we're trying to produce...I'll bold/underline the ones that DON'T come out of the box:

      At-home video on a mobile device
      At-Home video on a PC
      At-home continuous audio on a mobile device
      At-home continuous audio on a PC
      At-home continuous audio/video on a mobile device
      At-home continuous audio/video on a PC
      At-home notifications of events in the room
      At-home motion control of the camera
      Remote viewing with access control
      Remote video on a mobile device
      Remote video on a PC
      Remote continuous audio on a mobile device
      Remote continuous audio on a PC
      Remote continuous audio/video on a mobile device
      Remote continuous audio/video on a PC
      Remote notifications of events in the room
      Remote motion control of the camera
      Remote 2-way communication with the baby on a mobile device

I have a web server on my LAN, so I actually demo a lot of my changes on a supplemental web server (so I'm not modifying the actual web server on the device...for now).

Now, if you look at the box...they'll tell you some of these exist. But they don't. For example, their web service has a way to view remotely, but it's either full access or no access, and it's to one account. So if I wanted to let my mom log in and see the baby, she can now take over control of the camera, flash the firmware, etc. As we'll find later...it has a SECRET web interface built in (LAN Only)...that has ABSOLUTELY NO ACCESS CONTROL FOR BOTH VIEWING AND CONTROLLING. Badness. Lastly, the video times out after 5 minutes. The notifications happen still, which I think is nice, but wife wants constant-audio, which it cannot provide.

So, based on Simon's work above, the camera actually hosts a tiny little web interface. It USED to have an index.html...but no longer does. Maybe this was a security thing, as they intended to discontinue the web interface? Either way, the URL for that is now blinkhome.html so you can get to it. In this, there's 2 pages I've found so far, a "video only" page (blinkhome.html) and an audio/video page (java.html). BTW...a link to the "video only" page is still pointing to index.html, which lends credence to the idea that they just changed it and didn't think more about it.

First, let's talk the "video only" page. It's not video at all. It loads a series of static images, 3 at a time, and layers them, then when the last one is finished loading it deletes the front one. The IMPACT is it looks like a video. It's all done with Javascript. Now, it actually indexes the images, and uses the index as the layer. This means, if you leave it running forever...it's POSSIBLE that the index number and layer go into the BILLIONS range or higher. For all I know...this could actually eventually lead to a memory error. The image is actually not an image per-se...it's called from a function in the root of the web server, with the address http://IP/?action=snapshot . Now...as a test, I port forwarded a random port to point to my camera on my router...I get a 501 error, "Lacking Session Key" which I'm working on understanding now.

Below are the code snippets for the brunt of the javascript work to make this go:


function createImageLayer() {
 var img = new Image();
 img.style.position = "absolute";
 img.style.zIndex = -1;
 img.onload = imageOnload;
 img.onclick = imageOnclick;
 img.width = 640;
 img.height = 480;
 img.src = "/?action=snapshot&n=" + (++imageNr);
 var webcam = document.getElementById("webcam");
 webcam.insertBefore(img, webcam.firstChild);
}
// Two layers are always present (except at the very beginning), to avoid flicker
function imageOnload() {
 this.style.zIndex = imageNr; // Image finished, bring to front!
 while (1 < finished.length) {
  var del = finished.shift(); // Delete old image(s) from document
  del.parentNode.removeChild(del);
 }
 finished.push(this);
 current_time = new Date();
 delta = current_time.getTime() - previous_time.getTime();
 fps = (1000.0 / delta).toFixed(3);
 previous_time = current_time;
 if (!paused) createImageLayer();
}

This is done so that you're never waiting for a redraw. You always have an image up, and the one loading is behind it. You can see that imageNr variable gets out of hand...I'm thinking about implementing something like "If imageNr gets to 10,000 then set it to 1" which will set the number over. It would create a single potential flicker for one frame...if we consider that it seems to run at about 25fps, that's 1 flicker, for 1/25th of a second, every 7 minutes. I think that's reasonable to achieve "always on" functionality for the monitor.

This "video only" page also has ALL the controls listed, and NO access control. That's absurd. I often leave on vacation and leave a camera on for my dog, and then my parents can look in (since they don't travel much). I wanted to have this feature, but the only ways it seems to provide access are with an account (it's 1 account/camera pair, so I'd have to give viewers my real login to plug into an app OR the Motorola web interface) or to point them to the totally unprotected URL on the LAN. Did I mention you can talk to the baby and play music to the baby from the camera? I don't want strangers doing that! My goal here is to use a supplemental web server to route content, where I can implement access control.

Now, there's the 2nd page. This is http://IP/java.html. This one actually has an embedded java player which plays streaming audio and video. Now...I use chrome on linux, and since NPAPI is now depreciated I can't play the applet, but since wife uses windows, I was able to view it there. Looking at the source, you see the following code block:


<applet code="com.charliemouse.cambozola.Viewer" archive="/cambozola.jar" width="512px" height="384px" >
 <param name="url" value="/?action=appletvastream"/>
 <param name="watermarks" value="/favicon.png|left,top"/>
</applet>

Cambozola...a little googling, voila. It's an open source embedded java video player: website. This tells me, if we can find the stream, maybe we can use an HTML5 player in a website to pass access. This could be the holy grail.

So, I took the address for the stream and plugged it into VLC...BOOM video. Viewing the codec information, it's a Motion JPEG Video (MJPG) that is decoded into Planar 4:2:0 YUV full scale. In VLC...I get no audio whatsoever. On a hunch, I plugged the following streams into VLC as well: /?action=appletvstream and /?action=appletastream Both resolve! Now, the audio doesn't produce anything, but it seems there's 3 options for streams. The audio stream is exactly what the wife is looking for, so this is where we need to focus the research.

When I load the java applet on wife's windows computer, I get audio. When I load the stream in VLC on the same computer, no audio. Now, the links all resolve in VLC, so I have to assume that the reason it works in the java player is that they have baked some sort of custom codec into the Cambozola player. I'll need to pull this out.

So, let's go back to Cambozola. Technically, it's GPL v2, so the source should be available, right? HMM NOT FINDING IT OOPS, Stallman is goan be PISSED. Nevertheless, I have a Java decompiler, so I'm good. It seems they baked in v0.80. On cambozola's website, it doesn't link to it directly, but the code is there: http://www.charliemouse.com:8080/code/cambozola/cambozola-0.80.tar.gz If we open up the .jar included with the camera, there's 2 suspicious classes which are not in the stock cambozola jar...ADPCM and ADPCM Decoder. There's another one called "PlayAudio" which I'm interested in. Since this is all GPLv2...let's dig in:

PlayAudio.Class


package com.charliemouse.cambozola.shared;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
public class PlayAudio extends Thread
{
 private AudioFormat m_audfm;
 private SourceDataLine m_line;
 private boolean m_bRunAudio;
 private boolean m_bPlayAudio;
 private int m_aud_idx;
 private int m_audRec_idx;
 private byte[][] m_AudBuf;
 private int[] m_AudBufLen;
 private int[] m_AudBufStatus;
 public PlayAudio()
 {
  this.m_bRunAudio = true;
  this.m_bPlayAudio = true;
  this.m_aud_idx = 0;
  this.m_AudBuf = new byte[16][16160];
  this.m_AudBufLen = new int[16];
  this.m_AudBufStatus = new int[16];
  this.m_audRec_idx = 0;
  this.m_audfm = new AudioFormat(8000.0F, 16, 1, true, false);
  try
  {
   this.m_line = AudioSystem.getSourceDataLine(this.m_audfm);
   this.m_line.open(this.m_audfm, 16160);
  }
  catch (LineUnavailableException e)
  {
  }
 }
 public void init()
 {
 }
 public void setPlayAudio(boolean bPlayAudio)
 {
  this.m_bPlayAudio = false;
  for (int i = 0; i < 16; i++)
  {
   if (this.m_AudBufStatus[i] != 0)
   {
    Util.memSet(this.m_AudBuf[i], 1010, (byte)0);
   }
  }
 }
 public void getAudio(byte[] audioData, int audioLen) {
  int i = 0;
  if (!this.m_bPlayAudio) {
   return;
  }
  if (audioLen > 4040)
  {
   return;
  }
  while (i < audioLen)
  {
   int len;
   int len;
   if (i + 16160 <= audioLen)
    len = 16160;
   else {
    len = audioLen - i;
   }
   if (this.m_AudBufStatus[this.m_audRec_idx] == 0)
   {
    this.m_AudBufStatus[this.m_audRec_idx] = 1;
    System.arraycopy(audioData, i, this.m_AudBuf[this.m_audRec_idx], 0, len);
    this.m_AudBufLen[this.m_audRec_idx] = len;
    this.m_AudBufStatus[this.m_audRec_idx] = 2;
    i += len;
    this.m_audRec_idx = ((this.m_audRec_idx + 1) % 16);
   }
   else
   {
    try
    {
     Thread.sleep(100L);
    } catch (InterruptedException ie) {
     break;
    }
   }
  }
 }
 public void run()
 {
  this.m_line.start();
  while (true) if (this.m_bRunAudio)
   {
    if (this.m_bPlayAudio == true)
    {
     if (this.m_AudBufStatus[this.m_aud_idx] != 2) {
      try
      {
       Thread.sleep(20L);
      } catch (InterruptedException ie) {
       break label176;
      }
     }
     this.m_AudBufStatus[this.m_aud_idx] = 1;
     if (this.m_AudBufLen[this.m_aud_idx] > 0)
     {
      this.m_line.write(this.m_AudBuf[this.m_aud_idx], 0, this.m_AudBufLen[this.m_aud_idx]);
      this.m_AudBufLen[this.m_aud_idx] = 0;
      this.m_AudBufStatus[this.m_aud_idx] = 0;
      this.m_aud_idx = ((this.m_aud_idx + 1) % 16); continue;
     }
     this.m_AudBufStatus[this.m_aud_idx] = 0;
     this.m_aud_idx = ((this.m_aud_idx + 1) % 16); continue;
    }
    try
    {
     Thread.sleep(1000L);
    }
    catch (InterruptedException ie)
    {
    }
   }
  label176: this.m_line.drain();
  this.m_line.flush();
  this.m_line.close();
 }
}

Now...I wanted to get some info about the actual web server this is running...so I ran this command:


surfrock66@sr66-darter:~/Downloads$ wget --save-headers 192.168.1.68/blinkhome.html
--2014-08-16 13:43:34-- http://192.168.1.68/blinkhome.html
Connecting to 192.168.1.68:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified
Saving to: ‘blinkhome.html’
[ <=> ] 11,594 --.-K/s in 0.03s
2014-08-16 13:43:34 (416 KB/s) - ‘blinkhome.html’ saved [11594]
surfrock66@sr66-darter:~/Downloads$ cat blinkhome.html
HTTP/1.0 200 OK
Content-type: text/html
Connection: close
Server: MJPG-Streamer/0.2
Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0
Pragma: no-cache
Expires: Mon, 3 Jan 2000 12:34:56 GMT

So...this is "MJPG-Streamer" version 0.2. Turns out, open source! GPLv3! https://code.google.com/p/mjpg-streamer/ Now we maybe can get into the API a bit. Right off the bat, we see a new stream address...http://IP/?action=stream which DOES resolve in VLC...but still no sound. That I can tell, mjpg-streamer comes with no audio support...I imagine the stream calls to appletvastream and appletastream are custom by Motorola.

In digging through the source, I found this: https://code.google.com/p/mjpg-streamer/source/browse/trunk/mjpg-streamer-1.6.3/plugins/input_uvc/dynctrl.c If we could get the modified source from Motorola, we could probably identify the specific commands this camera can accept...including the audio in, playing lullabyes, etc.

Now that we've identified the 2 open source components, which have both been heavily modified...I think it's time to chase Motorola down, as they're now legally obliged to provide us the modifications they made to these 2 utilities. I opened the manual included...no info about the GPL v2 or v3 at all. I submitted a ticket to Motorola...and pinged the FSF in case Motorola won't cough up the source.

Here's a reference to the pages I've found on the device so far:

  • http://IP/index2.html
  • http://IP/blinkhome.html
  • http://IP/java.html
  • http://IP/javascript.html
  • http://IP/routersetup.html
  • http://IP/wifisetup.html
  • http://IP:8080/fwupgrade.html
  • http://IP:8080/cgi-bin/online_upgrade
  • http://IP:8080/cgi-bin/upload
  • http://IP:8080/style.css
  • http://IP:8080/fix.css
  • http://IP:8080/functions.js
  • http://IP:8080/cambozola.jar

Here's the list of actions that you can pass to http://IP/?action=__

  • snapshot - Take a picture
  • stream - Stream video only
  • command - Send a command
  • appletvstream - Stream video only
  • appletastream - Stream audio only
  • appletvastream - Stream audio and video

Here's the list of Commands that you can pass to http://IP/?action=command&command=__ (Many of these were pulled from the source code for mjpg-streamer, and were probably depreciated with Motorola's modifications)

  • reset_pan_tilt - Reset Camera Position (NO EFFECT, returns -1)
  • value_contract - Unknown
  • value_contrast - Retrieve Contrast Value (NO EFFECT, returns -1)
  • value_brightness - Retrieve Brightness
  • value_temperature - Retrieve Temperature
  • value_resolution - Retrieve Resolution
  • contrast_plus - Increase Contrast
  • contrast_minus - Decrease Contrast
  • brightness_plus - Increase Brightness
  • brightness_minus - Decrease Brightness
  • fb_stop - Stop ongoing up-down tilt command
  • move_backwardx.y - Tilt Up, amount x between -3 and 3, y between 0-9
  • move_forwardx.y - Tilt Down, amount x between -3 and 3, y between 0-9
  • lr_stop - Stop ongoing left-right tilt command
  • move_leftx.y - Tilt Left, amount x between -3 and 3, y between 0-9
  • move_rightx.y - Tilt Right, amount x between -3 and 3, y between 0-9
  • VGA640_480 - Set resolution to 640x480
  • QVGA320_240 - Set resolution to 320x240
  • QQVGA160_120 - Set resolution to 160x120
  • led_on - Turns the LED ON (NO EFFECT, returns -1)
  • led_off - Turns the LED OFF (NO EFFECT, returns -1)
  • led_auto - (NO EFFECT, returns -1)
  • led_blink - Blinks the LED (NO EFFECT, returns -1)
  • reset_factory - Factory Reset the camera
  • pan_plus - Move Right (NO EFFECT, returns -1)
  • pan_minus - Move Left (NO EFFECT, returns -1)
  • pan_set&value=## - Set Pan Position (NO EFFECT, returns -1)
  • tilt_minus - Move Down (NO EFFECT, returns -1)
  • tilt_plus - Move Up (NO EFFECT, returns -1)
  • tilt_set&value=## - Set Tilt Position (NO EFFECT, returns -1)
  • setup_wireless_read - Unknown (NO EFFECT, returns -1)
  • dummy_request - Unknown (NO EFFECT, error page)
  • gain_plus - Increase Gain (NO EFFECT, returns -1)
  • gain_minus - Decrease Gain (NO EFFECT, returns -1)
  • focus_plus - Increase Focus (NO EFFECT, returns -1)
  • focus_minus - Decrease Focus (NO EFFECT, returns -1)
  • focus_set&value=## - Set Focus Value (NO EFFECT, returns -1)
  • saturation_minus - Decrease Saturation (NO EFFECT, returns -1)
  • saturation_plus - Increase Saturation (NO EFFECT, returns -1)
  • reset - Unknown (NO EFFECT, returns -1)

I tried futzing with just recreating the interface with the correct buttons...this doesn't work. You're welcome to peek in here. So...turns out, it's a CORS thing...the web server on the camera would need to be updated to allow for cross-origin access, read more here: http://www.html5rocks.com/en/tutorials/cors/ For sure, we would need to modify the web server to continue on some of the things I'm trying to do.

Related Posts:


15 Responses to “Improving the Motorola Blink Baby Monitor/Camera”

  1. john Says:
    avatar

    Great stuff, great continuation of Simon’s site. What I’m really trying to do is edit the firmware on the camera–I’m sure it’s possible to change one of the audio files located within the camera to something more useful.

    Keep posting, I’ll keep reading.

  2. Surfrock66 Says:
    avatar

    I’m about to post my part 2…I’m still waiting on this. I have the firmware, and I see the files…it’s got to be as simple as overwriting the wav files. I actually tried to recompile the firmware by making my own romfs bin with a new page on the web server…the filesize was much bigger and it never flashed. I have a question into a moto developer but I don’t know if i’ll get a response.

  3. baker Says:
    avatar

    Thank you for doing this.
    The PTZ and lullaby commands work on the IOs applications. To see the HTML commands you can sniff the air with AirPcap or a wireless LAN controller. Some AP will also let you sniff to a cap file. Then use wire shark to decode the packets and reassemble the data streams.

  4. Bob Says:
    avatar

    Great stuff here. Any understanding of how to access the camera outside of the LAN? There is a camera ID printed on the camera, and I am wondering if there is some combination of the ID and a monitoreverywhere domain name that will resolve to the camrea.

  5. Surfrock66 Says:
    avatar

    Working on it. From a very baseline level, port forward 80 to it from some other port on your router (choose a random one so creeps can’t get in). Then you can access it from remotely, using the blinkhome.html. You get no audio at this point. BTW…I use a cool cpanel script to always update a subdomain of this website to point to my home IP; if comcast changes my IP, from inside the network I have a cronjob on my linux server ready to communicate it back up to cpanel.

    Now, if Motorola EVER gets me the source, there’s 2 things. By allowing Cross orign resource sharing, we can embed the actual images in a page and then you can implement access control on that page (so, I could have surfrock66.com/babycam with an embedded feed which you need to enter a password to reach). The other problem is the java player…which I can replace with a custom html5 one, if I can figure out what they’re doing with the audio. The audio is still a complete mystery. mjpg-streamer has no audio support, but they’ve added it, and even though VLC can’t hear it…their custom cambozola spin can. Once they open up the source (again, both are GPL so they HAVE to publish their changes) I think I’ll be able to either replace the player, or possibly update the actual streamer to do something cooler like mp3 for easier compatibility.

  6. Dan Says:
    avatar

    I just got one from meh.com, a Blink1-S.
    It’s fresh out of the box and I haven’t downloaded any phone apps or set it up yet. I turned it on and noticed that it’s broadcasting an SSD with the same name as the “ID” printed on the bottom, “Camera-xxxxx”. I connected my computer to it and I got a 192.168.2.10 ip address. The 192.168.2.1 address (camera) has port 80 and port 8080 open according to nmap. I try to connect my web browser to both ports and it asks for a user name and password. I can’t figure it out though, so I can’t get in. Any clues?

  7. Surfrock66 Says:
    avatar

    No idea, mine didn’t ask for a login, can you try 192.168.2.1/blinkhome.html or 192.168.2.1/java.html ?

  8. jon Says:
    avatar

    Try user / pass as the username / PW requirements.

    Looks like the same base firmware described for the motorola FOCUS66 @ http://atom0s.com/forums/viewtopic.php?f=2&t=45

  9. mahmoud Says:
    avatar

    In the new models of this monitor.
    they have secured the camera with very hard to predict user and password for web interface.

  10. mahmoud Says:
    avatar

    Could anyone tell if he has any idea about these default credentials or where can i get them from ??
    Thanks for advice

  11. Kaim Says:
    avatar

    Hay.
    user
    pass

    and it works!

  12. kaim Says:
    avatar

    OK. more info on Google.
    Your hack is much better than those, but any way
    http://blok.tiyun.de/2015/view-your-hubble-camera-stream-whereever-you-want
    http://atom0s.com/forums/viewtopic.php?t=45
    http://simon.aldrich.eu/blog/2013/08/motorola-blink1-hacking/

  13. Paul Says:
    avatar

    I’ve written some instructions to embed the live feed in a web page having picked up various bits from here and there, this page included. You can check it out at http://blackchili.co.uk/tech-info/focus66embed/ – Paul

  14. Paul Says:
    avatar

    Ignore above for now – my camera got its firmware auto-updated last night and now it no longer works. D’oh!

  15. Surfrock66 Says:
    avatar

    The fact that the running code unpacks in each run SUCKS. It means each change has to be a firmware update…I tried doing one of those with a custom firmware…it didn’t go well and I bricked the camera 🙁

Leave a Reply