Monday, January 19, 2009

Pentester trick #7: re-enabling CMD & REGEDIT

There are 2 settings that are commonly used by system administrators in "restricted", kiosk-like environments: DisableCMD and DisableRegistryTools, which are both to be found under:

HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System

As their names imply, those settings disable the use of CMD.EXE and REGEDIT.EXE.

Those settings are enforced by CMD and REGEDIT themselves. Therefore, alternatives such as Console and Registry Workshop will still run fine. However, it might not always be handy to bring new applications on the target system. So, how do we recover CMD and REGEDIT applications locally ?

It would be easy to find binary checks inside both applications and to patch them, but a good pentester is lazier than that.

After making a copy of both applications, it is enough to replace a single character within "DisableCMD" or "DisableRegistryTools" strings. I really love those stupid tricks :) The question is "how ?" ... and surprisingly, the answer is not obvious.
  • DEBUG/EDLIN: they won't handle files over 64KB.
  • ".COM" application written in pure assembly using DEBUG: cool, but a bit tedious.
  • QBASIC application: there is no QBASIC shipped with Windows any more :(
  • Notepad/Wordpad: they mess up binary files on write back.
  • VBScript: is poor at handling binary files.
  • VBA inside an Office application: cool, but you need to have Office installed beforehand.
  • NTSD: does not support the .readmem/.writemem commands.
In most cases, the best course of action is to run CMD inside NTSD (hint: you can drag-and-drop CMD over NTSD, which is sometimes handy in very restricted "kiosk" modes):

C:\> ntsd cmd.exe
(...)
0:000> lm
start end module name
4ad00000 4ad64000 cmd (deferred)
77be0000 77c38000 msvcrt (deferred)
77ef0000 77f37000 gdi32 (deferred)
7c800000 7c905000 kernel32 (deferred)
7c910000 7c9c7000 ntdll (export symbols) ntdll.dll
7e390000 7e420000 user32 (deferred)

0:000> s 4ad00000 L 64000 44 00 69 00 73 00 61 00 62 00 6C 00 65 00 43 00 4D 00

4ad14944 44 00 69 00 73 00 61 00-62 00 6c 00 65 00 43 00 D.i.s.a.b.l.e.C.


0:000> e 4ad14944 41


0:000> g

(...)
Microsoft Windows XP [version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\temp>


I'll be glad if someone comes with another solution :)

Note: surprisingly, the "DisableCMD" string lies within the code (".text") section.

Note for kiosk designers: to prevent users from running arbitrary applications, Software Restriction Policies would scale more easily.

Monday, January 05, 2009

Lessons learned from MS08-005

[ This post has been 80% complete for 1 year. And I swear the cleanup my "todo list" in 2009 :) ]

MS08-005 (KB 942831) is a local privilege escalation bug affecting:
  • IIS 5.0 (Windows 2000)
  • IIS 5.1 (Windows XP)
  • IIS 6.0 (Windows 2003)
  • IIS 7.0 (Vista)
This particular bug caught my attention for several reasons:
  • Local bugs tend to be more easily and reliably exploitable ;
  • The bugfix is very small in size ;
  • This bug made up his way into Vista, despite manual and automated code analysis.
Let's play with Windows XP SP2 version of this bug!

BinDiff-ing

First step is to install the patch, and to recover backuped files. In our case, there is only 1 file (infocomm.dll), that can be found in:
C:\windows\$NtUninstallKB942831$

Second step is to diff both original and patched files. If you happen to have a legit IDA Pro copy, Tenable PatchDiff2 is the best free plugin available out there. Otherwise, you'll have to fall back on eEye Binary Diffing Suite. Screenshots below are taken from BinDiff2.

Only 2 functions were modified by the patch:
int __stdcall CVRootDirMonitorEntry::FileChanged(char *lpString2, int) int __thiscall CVRootDirMonitorEntry::ActOnNotification(unsigned long, unsigned long)

Understanding the change

We will focus on FileChanged() function, in which strlen()/strcpy() operations were fixed (as shown in the graph below). Note: both APIs were inlined.



Code changes can also be spotted using the excellent Hex-Rays decompilation plugin.

Unpatched version:

if ( _strchr(input_filename, '~') )
{
result = ConvertToLongFileName(*((char **)long_filename + 3), input_filename, &FindFileData);
if ( !
result )
return
result;
v7 = _strrchr(input_filename, '\\');
if (
v7 )
{
v9 = v7 - input_filename;
v15 = v7 - input_filename + 1;
v16 = v7 - input_filename + 1;
v15 >>= 2;
memcpy(&v25, input_filename, 4 * v15);
v17 = &input_filename[4 * v15];
v18 = &v25 + 4 * v15;
v19 = v16 & 3;
v8 = FindFileData.cFileName;
memcpy(v18, v17, v19);
do
v20 = *v8++;
while (
v20 );
memcpy(&v26[v9], FindFileData.cFileName, v8 - &FindFileData.cFileName[1] + 1);
input_filename = &v25;
}

Patched version:

if ( _strchr(v4, '~') )
{
result = ConvertToLongFileName(*(char **)(v3 + 12), v4, &FindFileData);
if ( !
result )
return
result;
v7 = _strrchr(v4, '\\');
if (
v7 )
{
v9 = v7 - v4 + 1;
memcpy(v24, v4, v7 - v4 + 1);
v8 = FindFileData.cFileName;
v10 = 261 - v9;
do
v17 = *v8++;
while (
v17 );
if (
v8 - &FindFileData.cFileName[1] + 1 < v10 )
{
v11 = FindFileData.cFileName;
do
v18 = *v11++;
while (
v18 );
v10 = v11 - &FindFileData.cFileName[1] + 1;
}
v3 = v22;
memcpy(&v24[v9], FindFileData.cFileName, v10);
v25 = 0;
v4 = v24;
}


Runtime analysis

At this point, a little bit of runtime analysis using WinDbg could help us to get the whole picture.

Being attached to the inetinfo.exe process (where infocomm.dll is loaded, according to Sysinternals handle utility), we set a breakpoint on FileChanged():

0:016> .reload /f
Reloading current modules ..................................................................
0:016> bp CVRootDirMonitorEntry::FileChanged
0:016> bl

0 e 71ba7ca0 0001 (0001) 0:**** INFOCOMM!CVRootDirMonitorEntry::FileChanged
0:016> g


Given the name of the function under scrutiny, we suspect it will be called during file operations inside the Web root:

echo "hello" > c:\inetpub\wwwroot\test.txt

It worked! Moreover, we can confirm that the first argument passed to FileChanged(), which is of type char* according to debug symbols, is the filename.

Breakpoint 0 hit
eax=00000000 ebx=00000008 ecx=00712430 edx=007238c4 esi=007238a8
edi=007238a8
eip=71ba7ca0 esp=009cfed8 ebp=009cff0c
iopl=0 nv up ei pl zr na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 INFOCOMM!CVRootDirMonitorEntry::FileChanged:
71ba7ca0 8bff mov edi,edi

0:004> da poi(esp+4)

009cfee4 "test.txt"


Summary of our findings

Not everything makes sense for now, but we have gathered much interesting information during this preliminary analysis phase:
  • CVRootDirMonitorEntry::FileChanged() does (possibly insecure) string manipulation.
  • This function is called whenever a file under the Web root is "touched". The filename is passed as the first argument.
  • The offending code will be reached only if the filename contains the "~" character, and the "\" character (thus being inside a subdirectory).
  • ConvertToLongFileName() will be called in between on the filename.
A bug, really ?

At this point, we need to have a closer look at ConvertToLongFileName() internals.

According to debug symbols, the function prototype is:
int __stdcall ConvertToLongFileName(char *, LPCSTR lpString2, LPWIN32_FIND_DATAA lpFindFileData)

Implementation of this function is trivial: it takes the filename as an argument and uses FindFirstFileA() on it. The corresponding WIN32_FIND_DATA structure is passed back to the caller for future use.

MSDN documentation relative to FindFirstFile() is pretty straightforward. The WIN32_FIND_DATA structure is more interesting:

typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[MAX_PATH];
TCHAR cAlternateFileName[14];
}
WIN32_FIND_DATA,
*PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;


The caller will copy
FindFileDate.cFileName into a fixed size buffer of 264 bytes. Since MAX_PATH has a value of 260 on Windows platform, this is probably MAX_PATH+1 aligned to a DWORD. Where is the trick ?

Where Unicode comes into play

The trick is called Unicode. Quoting MSDN documentation:
In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters.
(...)
The Windows API has many functions that also have Unicode versions to permit an extended-length path for a maximum total path length of 32,767 characters.
What about CreateFile ?
The Unicode versions of several functions permit a maximum path length of approximately 32,000 characters composed of components up to 255 characters in length.
Therefore, it is possible to build a very long Unicode path, as long as each path token is less than 255 characters long. There is a little quirk in FindFirstFile() documentation here: cFileName cannot be longer than MAX_PATH, but the full path to this file can go far beyond MAX_PATH.

Do it yourself

Here are the steps to trigger the bug:
  • Using the mkdir command, create a directory inside C:\Inetpub\wwwroot with a long name (200 times 'A', for instance).
  • Using CreateFile("\\?\C:\Inetpub\wwwroot\AAA...AAA\BBB...BBB"), create inside this directory a file with a long name (200 times 'B', for instance). This API call must be Unicode-style, because the resulting full path will be longer than MAX_PATH.
  • Now access this file using its short name, as reported by the dir /x command. In this example, this would be something like echo toto > bbbbbb~1.
Et voilĂ  ! IIS should crash, because it expanded "aaa...aaa\bbbbbb~1" into "aaa...aaa" and "bbb...bbb" strings, that are thereafter concatenated into a stack-based buffer of size MAX_PATH.

Since infocomm.dll has been compiled with /GS option, a stack cookie prevents direct exploitation of this bug. Exploitation on IIS 5 is left as an exercise to the reader ;)

Conclusion

That was a very nice bug to study (even if it ended up in a trivial stack overflow) because it requires good knowledge of Windows internals.

As usual, it would be nice to know "how" this bug has been found by the original author. However, using Unicode filenames breaks so many applications out there that it could have been found by accident ;)

PS. Happy New Year to all readers !

Thursday, January 01, 2009

Having fun with certificates

Unless you went on vacation without WiFi access, your iPhone and your BlackBerry, you certainly have heard of the latest "Internet is dead" issue.

All details are available here and there. A comprehensive analysis is available on O'Reilly blog. A summary is available on ISC blog.

Now this is where C# beauty comes into play. Here is a code snippet that will check from local certificate store(s) the signature algorithm used. Everything that is not sha1RSA is displayed, because it should be bad (according to Microsoft analysis).

using System;
using System.Security.Cryptography.X509Certificates;

namespace SearchCerts
{
class Program
{
static void Main(string[] args)
{
// *** select appropriate store below ***
//var store = new X509Store(StoreName.My);
//var store = new X509Store(StoreName.AuthRoot);
//var store = new X509Store(StoreName.CertificateAuthority);
//var store = new X509Store(StoreName.Root);
//var store = new X509Store(StoreName.TrustedPeople);
//var store = new X509Store(StoreName.TrustedPublisher);

store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
foreach (var cert in store.Certificates)
{
if (cert.SignatureAlgorithm.FriendlyName != "sha1RSA")
{
System.Console.WriteLine("------------------------------");
System.Console.WriteLine("[FriendlyName]\t" + cert.FriendlyName);
System.Console.WriteLine("[Issuer]\t" + cert.Issuer);
System.Console.WriteLine("[Subject]\t" + cert.Subject);
System.Console.WriteLine("[Signature]\t" + cert.SignatureAlgorithm.FriendlyName);
}
}
store.Close();
System.Console.WriteLine("finished");
System.Console.ReadLine();
}
}
}


Here are some results from my certificate stores. Your mileage may vary.

Freephonie PKI is using MD5. End-users cannot submit CSRs by themselves, so the risk remains low. I'd be glad to know if the Freebox itself can send CSRs.

[FriendlyName]
[Issuer] O=Free, L=Paris, S=France, C=FR
[Subject] CN=1234567, O=Free, L=Paris, S=France, C=FR
[Signature] md5RSA


CNRS-Standard and CNRS-Plus PKI are using MD5. This is more concerning, because those are widely used authorities, and users can request certificates "at will".

[FriendlyName]
[Issuer] CN=CNRS, O=CNRS, C=FR
[Subject] CN=CNRS-Plus, O=CNRS, C=FR
[Signature] md5RSA

[FriendlyName]
[Issuer] CN=CNRS, O=CNRS, C=FR
[Subject] CN=CNRS-Standard, O=CNRS, C=FR
[Signature] md5RSA


Microsoft drivers signing PKI (at least on Windows XP SP2). Since drivers developers can ask for signatures, this is concerning too. But I feel that this authority might not be used by Microsoft anymore for newer signatures, given its old age.

[FriendlyName]
[Issuer] CN=Microsoft Root Authority, OU=Microsoft Corporation, OU=Copyright (c) 1997 Microsoft Corp.
[Subject] CN=Microsoft Windows Hardware Compatibility, OU=Microsoft Corporation, OU=Microsoft Windows Hardware Compatibility Intermediate CA, OU=Copyright (c) 1997 Microsoft Corp.
[Signature] md5RSA


Some random foreign authorities, which are using obscure certification policies.

[FriendlyName] NetLock Uzleti (Class B) Tanusitvanykiado
[Issuer] CN=NetLock Uzleti (Class B) Tanusitvanykiado, OU=Tanusitvanykiadok, O=NetLock Halozatbiztonsagi Kft., L=Budapest, C=HU
[Subject] CN=NetLock Uzleti (Class B) Tanusitvanykiado, OU=Tanusitvanykiadok, O=NetLock Halozatbiztonsagi Kft., L=Budapest, C=HU
[Signature] md5RSA


And last but not least, this VeriSign authority is using ... MD2 (this is not the only one, unfortunately).

[FriendlyName] VeriSign
[Issuer] OU=VeriSign Commercial Software Publishers CA, O="VeriSign, Inc.", L=Internet
[Subject] OU=VeriSign Commercial Software Publishers CA, O="VeriSign, Inc.", L=Internet
[Signature] md2RSA


PS. impots.gouv.fr is NOT vulnerable ;)