Wednesday, May 27, 2009

There is no Notepad trick

You might have heard that Notepad will fail to display correctly a file holding this single line: "this app can break" (or any sentence built on the same 4/3/3/5 scheme).



This issue could be tracked down to ANSI vs. Unicode text autodetection.

However, there is a much cooler Notepad trick in the latest issue of 2600 magazine. If the first text line happens to be ".LOG", Notepad will automatically append last modification time at the end of the file (as documented in KB81067). This feature is available from Windows 2.03.



This is not a pentester's trick by itself. But I still love it :)

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

Friday, December 19, 2008

Pentester trick #6: logging Internet Explorer boxes

In a previous post (Debugging Without Debugger), I promised to explain how to log content from text areas within Internet Explorer Web pages.

If you tried to apply the previously described technique to Internet Explorer, you should have noticed that GetWindowText is never called for getting text from Web controls. This is because the whole Web page is rendered without relying on standard Windows controls.

Therefore, we need to find the GetWindowTextLength/GetWindowText equivalents in MSHTML.DLL. Fortunately, debugging symbols will help us much in this case. The equivalent functions are:

mshtml!CTxtPtr::GetPlainTextLength()
mshtml!CTxtPtr::GetPlainText()

Which are called from 3 locations, the most common being:

mshtml!CElement::GetPlainTextInScope()

Unfortunately, NTSD does not seem to handle symbols properly, which prevents us from setting a symbolic breakpoint :(

Since MSHTML.DLL is upgraded by virtually every cumulative patch for Internet Explorer, you really need to get access to the debugging symbols for the specific Internet Explorer version installed on the target (hint: use the SYMCHK utility shipped with Debugging Tools) and find appropriate addresses inside.

From my up-to-date Internet Explorer 7 installation, here are some sample addresses:

mshtml!CTxtPtr::GetPlainTextLength : 0x44BB3B85 (entry point) -> 0x44BB3BFD (ret)
mshtml!CTxtPtr::GetPlainText : 0x44BB3C05 (entry point) -> 0x44BB3C75 (ret)
mshtml!CElement::GetPlainTextInScope : 0x44BB3CA6 (entry point) -> 0x44BB3D46 (ret)

The epilog of GetPlainTextInScope function is:

.text:44BB3D3F mov eax, [ebp+var_4]
.text:44BB3D42 pop edi
.text:44BB3D43 pop esi
.text:44BB3D44 pop ebx
.text:44BB3D45 leave
.text:44BB3D46 retn 4
.text:44BB3D46 ?GetPlainTextInScope@CElement@@QAEJPAVCStr@@@Z endp


From here, it is nice to know that ESI points to the Unicode text content before being overwritten at address 0x44BB3D43. Therefore the following NTSD commands will do the trick:

ntsd -pn iexplore.exe
bp 0x44BB3D43 "du poi(esi); g;"

Awkward trick I must admit, but it could save pentesters' lifes anyway ;)

Saturday, August 23, 2008

Reversing COM components

There are many free tools available that could prove helpful for analyzing COM components. My favorites are COMRaider and Jose Roca's TypeLib Browser.

Those tools are good for a 1st pass analysis (like fuzzing or calling a specific method from a VBS script), but when it comes to have a look at the binary implementation itself, things become a little thougher...

There are some IDA Pro helpers (scripts and plugins) hanging around, but given the complexity of COM and C++ reversing, it remains quite hard to tell where the code is through static analysis only.

Then I stumbled upon this post (by WebSense) that gives a very easy way to locate all exported methods through the use of #import directive in Visual Studio. Since they only give away screenshots, here is the full piece of code that will retrieve the RVA of the first 10 methods of Flash plugin.

#include <windows.h>
#include <stdio.h>

// Note: this must be a CPP file to use #import directive
#import "C:\\WINDOWS\\SYSTEM32\\Macromed\\Flash\\Flash9e.ocx" no_namespace

int main() {

printf("Hello, world of COM!\n");

CoInitialize(NULL);

IShockwaveFlash *pShockwave=NULL;

HRESULT hr = CoCreateInstance( __uuidof(ShockwaveFlash),
NULL,
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
__uuidof(IShockwaveFlash),
(void**)&pShockwave
);

if (hr==S_OK) {

DWORD dwVT=*(DWORD*)pShockwave;
DWORD *p=(DWORD*)dwVT;

for (int i=1;i<11;i++) {
printf("[%d] VA=%08x RVA=%08x\n",
i,
*p,
*p-(DWORD)GetModuleHandle("Flash9e.ocx")
);

p++;
}

pShockwave->Release();
}

return 0;
}


Sample output:

C:\>cl test.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86

Copyright (C) Microsoft Corporation. All rights reserved.

test.cpp
Microsoft (R) Incremental Linker Version 8.00.50727.762
Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.exe
test.obj

C:\>test.exe
Hello, world of COM!
[1] VA=300b4ec2 RVA=000b4ec2
[2] VA=300b38a4 RVA=000b38a4
[3] VA=300b38b1 RVA=000b38b1
[4] VA=300bd353 RVA=000bd353
[5] VA=300b78b7 RVA=000b78b7
[6] VA=300b7d33 RVA=000b7d33
[7] VA=300cbe5c RVA=000cbe5c
[8] VA=300c7c34 RVA=000c7c34
[9] VA=300c7c46 RVA=000c7c46
[10] VA=300c7b9d RVA=000c7b9d

Beware: the COM component will be instanciated by this code. Do not try this on malicious code, unless you know what you are doing!

Monday, August 18, 2008

MS08-051 secrets

On August 12th, Microsoft released a flurry of Office security patches.

Among those patches is to be found MS08-051 / Q949785, a patch targeting all supported versions of PowerPoint and PowerPoint Viewer, excluding PowerPoint Viewer 2007 and PowerPoint 2008 for Mac.

According to the bulletin, this patch fixes at least 3 vulnerabilities, 2 of them being documented on Reversemode.com. Let's have a look at the first vulnerability, which is an integer overflow resulting in a heap overflow. At the time of writing, a vulnerable version (11.0.5703.0) of PowerPoint Viewer 2003 can be downloaded from Microsoft web site. The vulnerable code path can be found in this version:

.text:300F642C loc_300F642C:
.text:300F642C mov eax, [edi]
.text:300F642E mov ecx, [ebp+var_14]
.text:300F6431 mov ebx, [eax+ecx*4]
.text:300F6434 mov esi, [ebx+2] ; EBX is user-supplied length
.text:300F6437 test esi, esi
.text:300F6439 mov [ebp+var_20], ebx
.text:300F643C mov [ebp+var_1C], esi
.text:300F643F jz loc_300F6516
.text:300F6445 mov ax, [ebx]
.text:300F6448 and eax, 3FFFh
.text:300F644D push eax
.text:300F644E call _MsoPopinfoGet@4 ; MsoPopinfoGet(x)

If EBX==0xFFFFFFFF, this code will result in calling GlobalAlloc(0x00000001) and copying 0xFFFFFFFF bytes later on.

After patching PowerPoint Viewer 2003, the code looks like (thanks to PatchDiff ;):

.text:300DC0BC loc_300DC0BC:
.text:300DC0BC mov eax, [edi]
.text:300DC0BE mov ecx, [ebp+var_14]
.text:300DC0C1 mov ebx, [eax+ecx*4]
.text:300DC0C4 mov esi, [ebx+2]
.text:300DC0C7 test esi, esi
.text:300DC0C9 mov [ebp+var_24], ebx
.text:300DC0CC mov [ebp+var_20], esi
.text:300DC0CF jz loc_300DC1B2
.text:300DC0D5 cmp [ebp+var_18], esi
.text:300DC0D8 jb loc_300DC1DD
.text:300DC0DE mov ax, [ebx]
.text:300DC0E1 sub [ebp+var_18], esi
.text:300DC0E4 and eax, 3FFFh
.text:300DC0E9 push eax
.text:300DC0EA call _MsoPopinfoGet@4 ; MsoPopinfoGet(x)


End of the story ? Not quite ... There is at least another Microsoft product that shares the PowerPoint codebase: Microsoft Office Live Meeting Client 2007.

Since it has PowerPoint rendering capabilities, this client is bundled with "lmpptview.dll". Beta versions of this DLL are internally numbered "12.0.x", showing clear connection with Office 2007. As of RTM version, this DLL is now numbered "8.0.3029.0". However, the following code sequence can be found inside:

.text:004345FC loc_4345FC:
.text:004345FC mov ecx, [ecx]
.text:004345FE lea eax, [ecx+edx*4]
.text:00434601 mov edi, [eax]
.text:00434603 mov esi, [edi+2]
.text:00434606 test esi, esi
.text:00434608 jz short loc_434689
.text:0043460A cmp [ebp-14h], esi
.text:0043460D jb loc_439769
.text:00434613 movzx eax, word ptr [edi]
.text:00434616 sub [ebp-14h], esi
.text:00434619 and eax, 3FFFh
.text:0043461E push eax
.text:0043461F call mightbe_MsoPopinfoGet


My bet is:
  • Live Meeting client is not vulnerable to this flaw, because the codebase comes from PowerPoint Viewer 2007.
  • And PowerPoint Viewer 2007 has been patched against this flaw since the beginning, whereas PowerPoint 2007 "Gold" and SP1 have been left vulnerable.
Men, that was close...