Feed on

Hacking a Peeple

A knock on your door

A common tactic for a burglary or casing a home is to send somebody unconcealed to the front door posing as a survey taker, lost pet owner, or some such and seeing if anyone is at home. If nobody answers it’s probably a sign that there’s nobody around and proceed.

Outdoor video cameras on the front porch can help, but they’re often mounted so high or at such a weird angle that it’s hard to get a good image to identify the person by their face. If you live in an apartment you might not even be able to put up an outdoor camera. Ever watch the evening news and hear “Police need help identifying this person, do you recognize their clothes or car”? I think the best solution to improve this is to aim a camera right in their face.

There are some solutions such as a “video doorbell” which has an exterior camera mounted lower, but the problem is that they are usually shiny and hi-tech looking, drawing attention to itself and it can easily be smashed. Ideally there would be nothing conspicuous from the outside and tamper proof. This is where an indoor door peephole camera comes in. It’s naturally face height and about as direct as you can get.

Ideally there would be a camera unit to mount on the door, have an LCD for local viewing (or at least easily removable if you actually want to look out), use wi-fi connectivity, and stream video to an existing security camera NVR. Surprisingly nothing like this exists. There are some video peephole viewers but they either don’t stream video, no wi-fi, use proprietary wireless, or they’re outside and easily bashed in. Or, you can buy just a peephole camera from Alibaba, but you have to build everything else. Axis makes some excellent pinhole IP cameras that could do the job, but they’re easily $300-$400. I tried building my own thing with a Raspberry Pi+camera module+3” LCD, but at least with the Pi2 the video lagged badly.

I’ve threatened to just find a cheap Android phone and stick it to my door. This gets me LCD, camera, wi-fi, streaming, at the price of a power cable taped to my door.

Enter the Peeple

I ran across the Peeple on Kickstarter a couple of years ago and it seemed promising so I chipped for it. It claimed to be a door mounted, wi-fi capable, battery operated camera. Whenever somebody knocked on the door, it would record video and send it to your phone. The downside was that it required “the cloud” to operate, there was no way to send video to my NVR (which already does iOS notifications). It didn’t have an LCD either, but it turned out to be easily removable so I’m not complaining too much.

After two years it finally arrived at my desk. And of course the first thing I did was tear it apart and sniffing network traffic to see what it did.

Sample video through a dusty peephole

Sample video through a dusty peephole

Network traffic

I found several surprising things when I fired up tcpdump and started looking at its network traffic. (I know, I shouldn’t be surprised at an IoT thing). For one it speaks plaintext HTTP, which is terrible for a IoT thing that speaks to the cloud, but great for me because it means I can see what it’s doing.
1) When motion is detected the radio and networking fire up, and it immediately sends a DHCP request.

2) It then sends a DNS request to for api.peeple.io, so it clearly disregards my DNS servers offered up in the DHCP reply and has Google’s resolver hard-coded. ugh. > 41216+ A? api.peeple.io. (31)
IP (tos 0x20, ttl 57, id 5688, offset 0, flags [none], proto UDP (17), length 91) > 41216 2/0/0 api.peeple.io. A, api.peeple.io. A (63)

3) Next, it fires off a HTTP GET request in plain text to /device/v1/knock/begin.
It sends some sort of hashed or encoded string as an X-Peeple: header, which is presumably based on my unit’s serial number or some other unique identifier. It’s not base64, so I suspect a hash. The server returns a UNIX timestamp and my phone gets an iOS notification.

IP > Flags [P.], seq 1:130, ack 1, win 5840, length 129: HTTP: GET /device/v1/knock/begin HTTP/1.1
E..............%4.B.Z..P...p,.7.P.......GET /device/v1/knock/begin HTTP/1.1
Host: api.peeple.io
User-Agent: Peeple
Accept: */*
X-Peeple: ycQfCce47hAwk...

IP > Flags [.], ack 130, win 18760, length 0
E .(..@.+..54.B....%.PZ.,.7.....P.IH.b..
IP > Flags [P.], seq 1:204, ack 130, win 18760, length 203: HTTP: HTTP/1.1 200 OK
E ....@.+..i4.B....%.PZ.,.7.....P.IH_...HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Tue, 31 Jan 2017 08:52:40 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 10
Connection: keep-alive
Access-Control-Allow-Origin: *


Amusingly I can craft a response by hand and send a barrage of notifications to my phone without any extra authentication:

[bwann@raptor ~]$ GET -H "X-Peeple: ycQfCce47hAwk..." -H "User-Agent: Peeple" http://api.peeple.io/device/v1/knock/begin

The server returns a UNIX timestamp of the current time.

4) After recording a few seconds of video, it does an HTTP POST request to upload the video file to /device/v1/knock/movie/<unix timestamp>. Ah hah! The payload is about 500k (466,259 bytes here)

IP > Flags [P.], seq 1:166, ack 1, win 5840, length 165: HTTP: POST /device/v1/knock/movie/1485852760 HTTP/1.1
E..............%4.B.Z..P...r.=.\P...v...POST /device/v1/knock/movie/1485852760 HTTP/1.1
Host: api.peeple.io
User-Agent: Peeple
Accept: */*
Content-Length: 466259
X-Peeple: ycQfCce47hAwk...

IP > Flags [P.], seq 166:1626, ack 1, win 5840, length 1460: HTTP
0x0000: 4500 05dc 000e 0000 8006 7b76 c0a8 8225 E.........{v...%
0x0010: 341b 42af 5ab5 0050 0000 1a17 fc3d d65c 4.B.Z..P.....=.\
0x0020: 5018 16d0 5f78 0000 e145 0000 b126 0000 P..._x...E...&..
0x0030: ffd8 ffe0 0010 4a46 4946 0001 0101 0000 ......JFIF......
0x0040: 0000 0000 ffdb 0043 0008 0606 0706 0508 .......C........

5) After uploading the video, it also sends a log file via HTTP POST to /device/v1/knock/log/<unix timestamp>. This revealed more interesting information about the unit.

IP > Flags [P.], seq 1:163, ack 1, win 5840, length 162: HTTP: POST /device/v1/knock/log/1485852760 HTTP/1.1
E....j.....,...%4.B.Z..P......P.P....g..POST /device/v1/knock/log/1485852760 HTTP/1.1
Host: api.peeple.io
User-Agent: Peeple
Accept: */*
Content-Length: 11929
X-Peeple: ycQfCce47hAwk...


sys 390 log_start:
inf 390 user_init: Peeple Firmware Started
.inf 390 user_init: -----------------------
.inf 390 user_init: version 1611152040
.inf 391 user_init: free heap 28652
.inf 394 user_init: rboot.mode 0
.inf 396 user_init: rboot.current_rom 1
.inf 399 user_init: rboot.previous_rom 0
.inf 402 user_init: rboot.fw_updated 0
.inf 405 user_init: rboot.is_first_boot 0
.inf 408 user_init: rboot.boot_attempts 0
.inf 411 user_init: rboot.rom[0] 0x11000
.inf 414 user_init: rboot.rom[1] 0x89000
.inf 417 user_init: rboot.rom[2] 0x0
.inf 420 user_init: rboot.rom[3] 0x0
.inf 423 configLoad: 652 bytes @ 0x1800
.inf 426 configLoad: index:0 ssid:XXXXXXXXXXX
.inf 429 configLoad: index:1 ssid:
.inf 432 configLoad: index:2 ssid:
.inf 435 configLoad: index:3 ssid:
.inf 438 configLoad: activeStation:0
.inf 441 configLoad: waitingForHandOff:0
.inf 444 heapReport: 28652
.inf 458 logResetInfo: cause: 6 (sys reset)
.inf 460 internetInit:
.inf 462 webServerInit: starting
.inf 465 webServerAddRequestHandler: url:[/peeple.log] -> 0x402b2420
.inf 474 webServerAddRequestHandler: url:[/crash] -> 0x402cc784
.inf 475 webClientInit: device:ycQfCce47hAwk....
.inf 479 otaUpdateInit: start
.inf 481 webServerAddRequestHandler: url:[/ota/status] -> 0x402b35e8
.inf 487 webServerAddRequestHandler: url:[/ota/update] -> 0x402b3640
.inf 493 webServerAddRequestHandler: url:[/reboot] -> 0x402b36b8
.inf 498 webServerAddRequestHandler: url:[/sleep] -> 0x402b35c0
.inf 503 wifiInit: starting
.inf 511 wifiSetup: ssid:Peeple XXXXXXX password:XXXXXXXXX
.inf 1401 webServerAddRequestHandler: url:[/wifi/status] -> 0x402b5a14
.inf 1401 webServerAddRequestHandler: url:[/wifi/scan] -> 0x402b5968
.inf 1402 webServerAddRequestHandler: url:[/wifi/connect] -> 0x402b5ebc
.inf 1408 webServerAddRequestHandler: url:[/wifi/forget] -> 0x402b5934
.inf 1414 webServerAddRequestHandler: url:[/wifi/reset] -> 0x402b58e4
.inf 1420 wifiTaskImpl: connect to ssid:XXXXXXXXXXX
.inf 1426 wifiTaskImpl: pending ssid:XXXXXXXXXXXXX
.inf 1428 cameraInit:
.inf 1429 webServerAddRequestHandler: url:[/camera/settings] -> 0x402b393c
.inf 1444 cameraTaskImpl: starting:CAMERA_MODE_KNOCK
.inf 1444 webServerAddRequestHandler: url:[/config/createHandOffKey] -> 0x402b6560
.err 1584 cameraReadBytes: timeout in RD_CHIP_ID (100)
.inf 1584 cameraTaskImpl: failed to get chipID, assuming baudrate is already set
.inf 1585 cameraTaskImpl: chipID:0x10006431
.inf 2328 doCameraModeKnock: we have a knock!
.inf 5406 heapReport: 15780
.inf 7422 onWifiStateChange: 0x0:EVENT_STAMODE_GOT_IP / STATION_GOT_IP
.inf 20252 doCameraModeKnock: numPictures:33 movieSize:466259 duration:17889
.inf 20252 doCameraModeKnock: (attempt 1) start new knock
.inf 20393 heapReport: 15316
.inf 21388 doCameraModeKnock: (attempt 1) upload 466259 bytes for knock 1485852760
.inf 21504 frameDataGenerator: 2920/466259

One bad thing I noticed is that it sends my wireless SSID to Peeple in PLAIN TEXT. While not an outright security hole, it’s an information leak that’s certainly none of their business.
Interestingly, lines 465-1414 told me the unit actually had an embedded webserver running. While the until was active I was able to go to and fetch the same file that was being uploaded.

6) Finally it does an HTTP GET of /device/v1/firmware/version/live, presumably to check if there’s any new firmware to download. The server returns an integer, which in this case matches version in line 390 of the log. Because I haven’t seen a firmware update yet, I don’t know what it does after this but assuming it would do another GET to fetch it.

Video format

It took me a while to figure out what video format this was, Wireshark wasn’t able to detect it. The JFIF plaintext and FF E0 00 10 4A was a tip off that it was some sort of JPEG video. After carefully extracting the video payload from Wireshark and removing the HTTP header, I fed it to VLC and it was clueless too.
Somebody later pointed out to me that FF D8 FF E0 was Motion JPEG. Sure enough after tweaking my process to include a few extra bytes I was able to extract the full video from the tcpdump! It’s about 15 seconds (15 frames) of 640×480 video. This means it’s not some obscure video format, and as long as I can intercept the traffic I can work with it.

Video interception

Getting the video directly instead of going to the cloud should just be a matter of pretending to be api.peeple.io and implementing my own endpoint to handle the HTTP POST and save the movie. But because it has a hardcoded DNS server, this isn’t so trivial.

So far I’ve done this:

  • Bound to a home Linux box
  • Configured BIND to listen on and be authoritative for peeple.io, returning my own IP address for api.peeple.io.
  • Configure a static route on my home router to send traffic destine to to my Linux box.
  • Whip up some Apache ScriptAlias directives to point to a python CGI handler

I haven’t finished writing scripts to spoof the HTTP GET/POST requests, but so far the unit happily goes along with my traffic interception. I see the requests landing in my Apache logs. Once I do this I can save it to my NVR or whatever and be cloud free! Alternatively I could send it to both myself and Peeple, preserving original app functionality.

I may just leave the interception in place permanently, just to see what hits it. I already run local caching DNS servers that will always be lower latency than going to the Internet. A few friends have already reported their random Google/Android devices also ignore their local DNS and go out to too, so I’m not alone.


There’s not a whole lot to this. Interesting all the pins and solder pads are well labeled, quite likely to aid with troubleshooting because it’s fresh out of a Kickstarter. The unit is about 3.5” in diameter and an inch thick. Right in the middle is a big lithium-ion battery.
For wi-fi connectivity, it appears to use an off-the-shelf ESP-12f module. This is over on the left side under the serial number sticker. I’m not sure how it receives the video data, it supports SPI, I2C, and GPIO. It has an embedded TCP/IP stack so that likely explains why it can’t do HTTPS (and certainly no IPv6) :(


There’s not much on the door-facing side, the camera, mini-USB port for charging, on/off and reset switches.


Underneath the battery is an ARM Cortex-M4 SoC, an STM32F411RE chip. AFAIK this is just a microcontroller, no embedded OS running. There look like there may be a UART exposed on the board, I need to play with them to see what I can discover.

ARM Cortex-M4

ARM Cortex-M4


Out of the box I already had one problem where after unplugging the charging cable the unit would not turn on. After I took it apart I figured out the battery connector was barely making contact with the battery while in the vertical position. I bent them inwards a bit and now it works fine. I emailed the inventor about it, he said he’s seen it before and in the latest email update to kickstarters he said they’d replace them if anyone else encountered this problem.

Overall it’s a neat little device, but not the perfect thing I wanted. The fact it uses a magnet to mount to the door plate is nice, so it’s easy to remove and look outside. Long battery life is nice for what it is, although personally I’d be fine settling with running a wire along the hinge side of the door for power if it got me live video all the time. I have to subvert networking to get it to send video to me, there’s no way to integrate a doorbell with it.

At least it’s not completely security stupid like other IoT devices which do things like leave your home network exposed to an alternative SSID, sending wi-fi passwords over the clear, have default logins (you can’t log in to this afaik), or turn into a spam bot.

Leave a Reply