Reversing the Dropcam Part 3: Digging into complied Lua functionality

Contribs from Nico Rodriguez, Kris Brosch, and Erik Cabetas

In Part 1 & Part 2 of this RE blog series you saw how we reverse engineered the Dropcam and got access to the file system. In this final post of the series we'll examine some of the binaries found on the file system and play a bit with Lua code we found there. As usual we'll talk about some of the lessons learned from some failures in the analysis process as well as successes. We'll conclude with a release of a small tool that can aid reversers who are looking at Lua disassembly.

The Lua code we found on the system is packed inside the Dropcam's /usr/connect binary which was obtained from the rooted Dropcam as described in our previous blog post (Part 2.) We unpacked the connect binary; it's compressed/packed with upx but that is trivial to undo. Once unpacked we loaded the binary in our trusty IDA and looked around a little bit. We noticed it was writing a file named /tmp/connect.bin and then running this command via a call to system():

rm -rf /tmp/connect && mkdir /tmp/connect && tar zx -f /tmp/connect.bin -C /tmp/connect && rm /tmp/connect.bin

So it looks like /usr/bin/connect is decompressing a tar.gz file hidden inside the connect binary itself. The IDA screenshot below shows the function that writes the file and then calls the shell command. This function is called with the arguments 0x8393c (the address of the connect.bin data in memory) and 0x29203 (the length of the file):

We extracted the file using dd:

dd if=./connect.decompressed of=connect.tar.gz bs=1 skip=473404 count=168451

And then, we unpacked the .tar.gz file and took a look at what was there:
$ ls -la

total 808
drwxrwxrwx 1 nico staff 4096 Feb 21 15:20 .
drwxrwxrwx 1 nico staff 4096 Nov 11 20:35 ..
-rwxrwxrwx 1 nico staff 1504 Apr 23 2013 containers.bin
-rwxrwxrwx 1 nico staff 5879 Apr 23 2013 decoder.bin
-rwxrwxrwx 1 nico staff 1038 Apr 23 2013 descriptor.bin
-rwxrwxrwx 1 nico staff 10376 Apr 23 2013 dispatch.bin
-rwxrwxrwx 1 nico staff 54727 Apr 23 2013 droptalk_pb.bin
-rwxrwxrwx 1 nico staff 9360 Apr 23 2013 encoder.bin
-rwxrwxrwx 1 nico staff 1243 Apr 23 2013 hello.bin
-rwxrwxrwx 1 nico staff 545 Apr 23 2013 hwver.bin
-rwxrwxrwx 1 nico staff 4279 Apr 23 2013 ir.bin
-rwxrwxrwx 1 nico staff 879 Apr 23 2013 list.bin
-rwxrwxrwx 1 nico staff 615 Apr 23 2013 listener.bin
-rwxrwxrwx 1 nico staff 650 Apr 23 2013 main.bin
-rwxrwxrwx 1 nico staff 2363 Apr 23 2013 monitor.bin
-rwxrwxrwx 1 nico staff 708 Apr 23 2013 motion.bin
-rwxrwxrwx 1 nico staff 2010 Oct 29 19:48 net.bin
-rwxrwxrwx 1 nico staff 2607 Apr 23 2013 oldiags.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_01_3D_hwrev_1.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_01_3D_hwrev_2.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_02_3D_hwrev_1.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_02_3D_hwrev_2.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_03_3D_hwrev_1.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_03_3D_hwrev_2.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_04_3D_hwrev_1.bin
-rwxrwxrwx 1 nico staff 17536 Apr 18 2013 ov9715_04_3D_hwrev_2.bin
-rwxrwxrwx 1 nico staff 3280 Apr 23 2013 persistence.bin
-rwxrwxrwx 1 nico staff 329 Apr 23 2013 platform.bin
-rwxrwxrwx 1 nico staff 3365 Apr 23 2013 platform_a5s.bin
-rwxrwxrwx 1 nico staff 551 Apr 23 2013 platform_local.bin
-rwxrwxrwx 1 nico staff 20750 Apr 23 2013 protobuf.bin
-rwxrwxrwx 1 nico staff 191 Apr 23 2013 rtp.bin
-rwxrwxrwx 1 nico staff 643 Apr 23 2013 settings.bin
-rwxrwxrwx 1 nico staff 9931 Apr 23 2013 states.bin
-rwxrwxrwx 1 nico staff 912 Apr 23 2013 status.bin
-rwxrwxrwx 1 nico staff 3822 Apr 23 2013 streams.bin
-rwxrwxrwx 1 nico staff 1505 Apr 23 2013 text_format.bin
-rwxrwxrwx 1 nico staff 1525 Apr 23 2013 type_checkers.bin
-rwxrwxrwx 1 nico staff 3047 Apr 23 2013 update.bin
-rwxrwxrwx 1 nico staff 601 Apr 23 2013 usb.bin
-rwxrwxrwx 1 nico staff 2602 Apr 23 2013 util.bin
-rwxrwxrwx 1 nico staff 1468 Apr 23 2013 watchdog.bin
-rwxrwxrwx 1 nico staff 3620 Apr 23 2013 wire_format.bin

Inspecting the first .bin file we see these are Lua byte-code files. The first five bytes were those of a Lua Bytecode Header:
| 1B | 4C | 75 | 61 | 52 | => Lua 0x52

These files contain compiled Lua bytecode that supplements the logic in the connect binary. From the initial examination, we saw the bytecode was Lua 5.2 bytecode. The structure of a Lua bytecode file is extensively documented; we'll just cover the necessary information in this post (for a quick overview take a look at this link).

Of course we'd like to know what functionality is hidden in these files so we tried every decompiler we could get our hands on. Unfortunately they all complained about the byte-code version or died trying to interpret the bytes on the files. This is because the decompilers weren't up-to-date for Lua 5.2. This version of Lua adds a couple of instructions to the VM but the semantics and the byte-code format seems to be the same.

Here were some of the decompilers we tried (among others):
Considering this, we tried to hack up the files to trick the decompilers into working with our target files but alas, nothing seemed to be working, the decompilers just died with errors stating that the chunk of code did not correspond to valid Lua code. Note: Pay careful attention to endianness when hacking up byte code files. We even considered patching a tool like unluac to support Lua 5.2 bytecode as this looked like the most mature out of the ones we tried, but this wouldn't be a trivial task and would require major surgery. Unluac and others weren't going anywhere without a major patch and we didn't have much time so we went lower-level to a bytecode disassembler.

Enter: LuaAssemblyTools(LAT) -
This Lua library allowed us to parse and disassemble the byte-code regardless of version and/or endianness. We were able to decompile the Lua 5.2 byte-code used in the connect binary into LASM (a LAT representation of Lua VM's instructions).

Now we have disassembly, but it's ugly -- like DNSSec level of ugly. So our next challenge was what to do with the dissasembled code. The way tables and constants are handled in Lua's VM is great for machine consumption but human readable it is not! How many levels of indirection can one really keep track of in their head at the same time?

Using LAT's LASM Decompiler we disassembled descriptor.bin into this:

; Decompiled to lasm by LASM Decompiler v1.0 ; Decompiler Copyright (C) 2012 LoDC ; Main code .name "" .options 0 0 1 2 ; Above contains: Upvalue count, Argument count, Vararg flag, Max Stack Size ; Constants .const "module" .const "descriptor" .const "FieldDescriptor" .const "TYPE_DOUBLE" .const 1 .const "TYPE_FLOAT" .const 2 .const "TYPE_INT64" .const 3 .const "TYPE_UINT64" .const 4 .const "TYPE_INT32" .const 5 .const "TYPE_FIXED64" .const 6 .const "TYPE_FIXED32" .const 7 .const "TYPE_BOOL" .const 8 .const "TYPE_STRING" .const 9 .const "TYPE_GROUP" .const 10 .const "TYPE_MESSAGE" .const 11 .const "TYPE_BYTES" .const 12 .const "TYPE_UINT32" .const 13 .const "TYPE_ENUM" .const 14 .const "TYPE_SFIXED32" .const 15 .const "TYPE_SFIXED64" .const 16 .const "TYPE_SINT32" .const 17 .const "TYPE_SINT64" .const 18 .const "MAX_TYPE" .const "CPPTYPE_INT32" .const "CPPTYPE_INT64" .const "CPPTYPE_UINT32" .const "CPPTYPE_UINT64" .const "CPPTYPE_DOUBLE" .const "CPPTYPE_FLOAT" .const "CPPTYPE_BOOL" .const "CPPTYPE_ENUM" .const "CPPTYPE_STRING" .const "CPPTYPE_MESSAGE" .const "MAX_CPPTYPE" .const "LABEL_OPTIONAL" .const "LABEL_REQUIRED" .const "LABEL_REPEATED" .const "MAX_LABEL" ; Upvalues .upval '' 1 0 ; Instructions gettabup 0 0 256 loadk 1 1 call 0 2 1 newtable 0 0 25 settable 0 259 260 settable 0 261 262 settable 0 263 264 settable 0 265 266 settable 0 267 268 settable 0 269 270 settable 0 271 272 settable 0 273 274 settable 0 275 276 settable 0 277 278 settable 0 279 280 settable 0 281 282 settable 0 283 284 settable 0 285 286 settable 0 287 288 settable 0 289 290 settable 0 291 292 settable 0 293 294 settable 0 295 294 settable 0 296 260 settable 0 297 262 settable 0 298 264 settable 0 299 266 settable 0 300 268 settable 0 301 270 settable 0 302 272 settable 0 303 274 settable 0 304 276 settable 0 305 278 settable 0 306 278 settable 0 307 260 settable 0 308 262 settable 0 309 264 settable 0 310 264 settabup 0 258 0 return 0 1 0 

To understand this as quick as possible we need something to make LASM a bit more sane, time to write some code to do it ourselves! Lua is a register-based virtual machine so that makes our life a little easier.

We made an easy script that rewrites the LASM code into something more human readable. It organizes the disassembly to a much more readable code display form, so consider the output of this tool somewhere in the middle of the spectrum between a straight disassembler and a decompiler (restructured disassembly?)

If you're interested to learn more, here are a few presentations showing the internals of a Lua VM that came in handy for this task ( and were a huge help).

The resulting code from our tool can't be compiled (so it's not a true decompiler) but it was so much easier to follow than a straight disassembly. You can find the tool published on our Github here.

Here we can see the description.bin output after using our script:

function main(...) module(descriptor) regs[0] = [] regs[0][TYPE_DOUBLE] = 1 regs[0][TYPE_FLOAT] = 2 regs[0][TYPE_INT64] = 3 regs[0][TYPE_UINT64] = 4 regs[0][TYPE_INT32] = 5 regs[0][TYPE_FIXED64] = 6 regs[0][TYPE_FIXED32] = 7 regs[0][TYPE_BOOL] = 8 regs[0][TYPE_STRING] = 9 regs[0][TYPE_GROUP] = 10 regs[0][TYPE_MESSAGE] = 11 regs[0][TYPE_BYTES] = 12 regs[0][TYPE_UINT32] = 13 regs[0][TYPE_ENUM] = 14 regs[0][TYPE_SFIXED32] = 15 regs[0][TYPE_SFIXED64] = 16 regs[0][TYPE_SINT32] = 17 regs[0][TYPE_SINT64] = 18 regs[0][MAX_TYPE] = 18 regs[0][CPPTYPE_INT32] = 1 regs[0][CPPTYPE_INwhT64] = 2 regs[0][CPPTYPE_UINT32] = 3 regs[0][CPPTYPE_UINT64] = 4 regs[0][CPPTYPE_DOUBLE] = 5 regs[0][CPPTYPE_FLOAT] = 6 regs[0][CPPTYPE_BOOL] = 7 regs[0][CPPTYPE_ENUM] = 8 regs[0][CPPTYPE_STRING] = 9 regs[0][CPPTYPE_MESSAGE] = 10 regs[0][MAX_CPPTYPE] = 10 regs[0][LABEL_OPTIONAL] = 1 regs[0][LABEL_REQUIRED] = 2 regs[0][LABEL_REPEATED] = 3 regs[0][MAX_LABEL] = 3 return regs[0] end

This gets the disassembly to the point where we can easily understand it, compared to what we had before which was just horrible. Now that we can disassemble the files we see that they control the logic of the device, but the hardware access is done at a lower level. More so, the System-on-a-Chip has some interesting features like setting up the parameters of your video input and output and the image post-processing is done by the hardware which is much more efficient.

Lua on an embedded devices such as Dropcam is compact and safer to write than C, so that's a good idea from the security front. The Linux kernel and it's device drivers running on the device take care of everything real-time related and they expose this functionality to Lua the Unix way i.e. everything is a file. You can open a /dev/ file to access the stream of video and manipulate camera functionality. Everything for image conversion, filtering, etc. is taken care of in the low-level drivers. (Note: a bit more detail on this topic can be be found in SynAck's recent presentation which was published after the research you're reading in this blog-post was conducted.)

This way of using Lua on embedded devices is a little different than project like eLua ( which takes the Lua VM and make it run on small embedded devices (to check the supported CPUs click We've seen that used on other embedded devices we hack on.

Well that's the conclusion of this blog post series, we hope you got a bit of insight into reversing embedded devices. We didn't publish any 0day vulns in these posts, 0days are a given in every product if you look hard enough, this blog series was meant to give the beginner/intermediate IoT reverser some guidance.

Reminder: You can find the Lua disassembly rewriter tool on our Github here.


  1. Great writeup! I found this after looking up more information about SynAck's work.

  2. Great glad you liked it, their work was focused more on exploitation. Ours was focused more on documenting some of the general RE process in detail.

  3. Combined Pumps have been in the chemical injection and transfer pump business for more than 20 years. Operating out of our manufacturing facility in the heart of Dyce Aberdeen we have been servicing the Oil & Gas industry worldwide providing Chemical Injection solutions, Pressure Test systems,
    and fluid transfer pumps.

  4. We invite you to come visit us and browse the latest LOCAL merch and store collection.
    We work with local businesses located in the UAE to source the best products and services across all of our concepts. barber in abu dhabi The relaxed venue is a great spot to stop in for a cuppa, or a haircut, or pricey pair of collectable shoes, but it’s the coffee that keeps us going back.

  5. Without you Papa and Mama, I can’t dream this life that you have gifted me. It is because of you that I’m living. Thank you and Happy New Year Wishes. I always want to be your son.

  6. We welcome you to stay with us and peruse the most recent LOCAL merchandise and store assortment. We work with Silicone Dish Washing Gloves neighborhood organizations Combined Pumps have been in the synthetic infusion and move siphon business for over 20 years.

  7. We invite you to remain with us and scrutinize the latest local product and store combination. Extraordinary happy you loved it, their work was centered more around abuse. Our Uk Essay Writing Services own was centered more around archiving a portion of the overall re cycles in detail.

  8. Appslure is an award-winning mobile app development company building feature-packed and interactive mobile applications for startups, medium and large enterprises.