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) – https://github.com/mlnlover11/LuaAssemblyTools.
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 (http://luaforge.net/docman/83/98/ANoFrillsIntroToLua51VMInstructions.pdf and http://www.dcc.ufrj.br/~fabiom/lua/ 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 LasmRewriter.py 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 (http://www.eluaproject.net/) which takes the Lua VM and make it run on small embedded devices (to check the supported CPUs click http://www.eluaproject.net/overview/status). 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.

3 thoughts on “Reversing the Dropcam Part 3: Digging into complied Lua functionality”

Leave a Reply

Discover more from Include Security Research Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading