The blog continues at suszter.com/ReversingOnWindows

Showing posts with label Security Testing. Show all posts
Showing posts with label Security Testing. Show all posts

January 30, 2016

Managed out-of-bound access in DeflateStream (.NET)

Recently I made a test to see the robustness of the Deflate algorithm in .NET Framework. It was written in Visual Studio 2013 using C# and DeflateStream class.

The test came back with one issue worth mentioning in this blog.

Background

IndexOutOfRangeException exception is thrown when the program tries to access out of the buffer boundaries. When this exception is thrown it indicates a missing or insufficient sanity check. When the sanity check works as expected it should prevent the exception from being thrown.

Microsoft explains like this.
Typically, an IndexOutOfRangeException exception is thrown as a result of developer error. Instead of handling the exception, you should diagnose the cause of the error and correct your code.
DeflateStream performs sanity check on the stream during the decompression and when a corrupted stream is detected InvalidDataException is being thrown.

The Issue

The issue the test hit is like this. During the decompression a specially crafted stream can bypass the sanity check and so the code doesn't throw InvalidDataException. Instead it tries to access out of boundaries and IndexOutOfRangeException is thrown.

The isolated testcase to reproduce the issue is like this.

using System;
using System.IO.Compression;
using System.IO;

namespace DeflateTestCase
{
    class Program
    {
        static Byte[] compressedData = {
            0x04, 0xDF, 0x03, 0x20, 0xFC, 0xA1, 0x6F, 0x85, 0xF2, 0x2B, 0xC5, 0xA4, 0xAA, 0x8A, 0xC8, 0xB4,
            0xDE, 0x3A, 0x5C, 0x06, 0xA2, 0x8C, 0xD9, 0x39, 0x41, 0xCB, 0xA6, 0x34, 0xDD, 0xCA, 0xC4, 0x2C,
            0x80, 0x7C, 0xCC
        };

        static void Main(string[] args)
        {
            using (MemoryStream compressedStream = new MemoryStream(compressedData))
            {
                MemoryStream resultStream = new MemoryStream();
                using (DeflateStream decompressionStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
                {
                    try
                    {
                        decompressionStream.CopyTo(resultStream);
                    }
                    catch (System.IO.InvalidDataException e)
                    {
                        System.Console.WriteLine("{0}", e.Message);
                    }
                    catch (System.IndexOutOfRangeException e)
                    {
                        System.Console.WriteLine("{0}", e.Message);
                        System.Console.WriteLine("{0}", e.StackTrace);
                    }
                }
            }
        }
    }
}

Stack trace.

c:\Dev\DeflateTestCase\bin\Release>DeflateTestCase.exe
Index was outside the bounds of the array.
   at System.IO.Compression.HuffmanTree.CreateTable()
   at System.IO.Compression.HuffmanTree..ctor(Byte[] codeLengths)
   at System.IO.Compression.Inflater.DecodeDynamicBlockHeader()
   at System.IO.Compression.Inflater.Decode()
   at System.IO.Compression.Inflater.Inflate(Byte[] bytes, Int32 offset, Int32 length)
   at System.IO.Compression.DeflateStream.Read(Byte[] array, Int32 offset, Int32 count)
   at System.IO.Stream.InternalCopyTo(Stream destination, Int32 bufferSize)
   at System.IO.Stream.CopyTo(Stream destination)
   at DeflateTestCase.Program.Main(String[] args) in c:\Dev\DeflateTestCase\Program.cs:line 24

Consequence

If DeflateStream is used (either implicitly or expicitly) to decompress data and the data can come from untrusted source IndexOutOfRangeException is expected to happen and it should be handled in order to prevent the program from abrupt termination (DoS).

The testcase can be downloaded from here.

UPDATE 31/January/2016 Further test confirm the same issue can be reached via a different code path from GZipStream.

Stack trace.

c:\Dev\GZipTestCase\bin\Release>GZipTestCase.exe
Index was outside the bounds of the array.
   at System.IO.Compression.HuffmanTree.CreateTable()
   at System.IO.Compression.HuffmanTree..ctor(Byte[] codeLengths)
   at System.IO.Compression.Inflater.DecodeDynamicBlockHeader()
   at System.IO.Compression.Inflater.Decode()
   at System.IO.Compression.Inflater.Inflate(Byte[] bytes, Int32 offset, Int32 length)
   at System.IO.Compression.DeflateStream.Read(Byte[] array, Int32 offset, Int32 count)
   at System.IO.Compression.GZipStream.Read(Byte[] array, Int32 offset, Int32 count)
   at System.IO.Stream.InternalCopyTo(Stream destination, Int32 bufferSize)
   at System.IO.Stream.CopyTo(Stream destination)
   at GZipTestCase.Program.Main(String[] args) in c:\Dev\GZipTestCase\Program.cs:line 26

The gzip testcase can be downloaded from here and the gzip file from here.

May 13, 2014

Security Implications of IsBad*Ptr Calls in Binaries

IsBad*Ptr [1] functions are to test whether the memory range specified in the argument list is accessible. Despite the fact they have been banned, they are still being referenced in many binaries shipped with popular applications.

In this post I'm describing the inner working of IsBad*Ptr, the steps the attacker may follow to abuse them, and mention few examples of binaries that have a reference to these banned functions.

Inner Working

When IsBad*Ptr is executed it first registers an exception handler. Then, it attempts to access to the memory specified in the argument list.

For example, IsBadReadPtr has the following instruction to read memory. ECX is the memory address specified in the argument list.

mov al,byte ptr [ecx]

If the instruction raises an exception, the execution is transferred to the exception-handler code. And IsBad*Ptr returns TRUE, meaning, it is a "bad" pointer because the data pointed by is inaccessible.

If the instruction executes without an exception being raised IsBad*Ptr returns FALSE.

Steps of Attacking

The attack against IsBad*Ptr looks like this.
  1. The attacker attempts to supply an invalid pointer parameter to IsBad*Ptr that returns TRUE.
  2. The attacker refines step #1 in a way that the supplied invalid pointer becomes valid due to a forced allocation for the location pointed by the invalid pointer. The attacker refines step #1 in a way that the supplied invalid pointer will point to valid memory location allocated by heap spray. And so, IsBad*Ptr returns FALSE leading to enter in an inconsistent state; that may or may not be an exploitable state.
  3. If the attacker can perform step #2 with IsBadWritePtr, when the call returns, it's expected to reach code that writes the location pointed by the pointer -- and that has attacker controlled data. And so, he reaches a presumably exploitable condition.
Referencing IsBad*Ptr can be easily checked during binary analysis and it is worthy to do.

Examples

This code snippet below can be found in msvbvm60.dll in Windows folder.

.text:72A0FEE5         push    38h                     ; ucb
.text:72A0FEE7         push    edi                     ; attacker supplies pointer was invalid before
.text:72A0FEE8         call    ds:IsBadReadPtr         ; and now it's valid because he's filled memory up
.text:72A0FEEE         test    eax, eax
.text:72A0FEF0         jnz     loc_72A0FF80            ; fall through
.text:72A0FEF6         mov     eax, [edi+4]
.text:72A0FEF9         mov     eax, [eax+4]
.text:72A0FEFC         mov     esi, [eax+8]            ; ESI is attacker controlled
.text:72A0FEFF         and     [ebp+arg_0], 0
.text:72A0FF03         mov     ax, [edi+2]
.text:72A0FF07         test    esi, esi
.text:72A0FF09         jz      short loc_72A0FF42      ; fall through
.text:72A0FF0B         movzx   ebx, ax
.text:72A0FF0E         mov     eax, [esi]              ; EAX is attacker controlled
.text:72A0FF10         push    esi
.text:72A0FF11         call    dword ptr [eax+0Ch]     ; EIP is attacker controlled


This one below can be found in dxtrans.dll in Windows folder.

.text:35C6142C         push    4                       ; ucb
.text:35C6142E         push    esi                     ; attacker supplies pointer was invalid before
.text:35C6142F         call    ds:__imp__IsBadWritePtr@8 ; and now it's valid because he's filled memory up
.text:35C61435         test    eax, eax
.text:35C61437         jz      short loc_35C61440      ; jump is taken
.text:35C61439         mov     eax, 80004003h
.text:35C6143E         jmp     short loc_35C6144A
.text:35C61440 loc_35C61440:                           ; CODE XREF: CDXBaseSurface::GetAppData(ulong *)+14
.text:35C61440         mov     eax, [ebp+this]
.text:35C61443         mov     eax, [eax+24h]
.text:35C61446         mov     [esi], eax              ; ESI is attacker controlled

The next code snippet is taken from v2.0.50727\mscorwks.dll in Windows folder. IsBadReadPtr is used to test the pointer that is passed to MultiByteToWideChar.

.text:7A0D17FB         push    eax                     ; ucb
.text:7A0D17FC         push    ebx                     ; attacker supplies pointer was invalid before
.text:7A0D17FD         call    ds:__imp__IsBadReadPtr@8 ; and now it's valid because he's filled memory up
.text:7A0D1803         test    eax, eax
.text:7A0D1805         jz      short loc_7A0D17A7      ; jump is taken
[...]
.text:7A0D17A7         cmp     edi, esi
.text:7A0D17A9         jle     short loc_7A0D17BF
.text:7A0D17AB         push    esi                     ; cchWideChar
.text:7A0D17AC         push    esi                     ; lpWideCharStr
.text:7A0D17AD         push    edi                     ; cbMultiByte
.text:7A0D17AE         push    ebx                     ; lpMultiByteStr - attacker's data
.text:7A0D17AF         push    1                       ; dwFlags
.text:7A0D17B1         push    esi                     ; CodePage
.text:7A0D17B2         call    ?WszMultiByteToWideChar@@YGHIKPBDHPAGH@Z ; WszMultiByteToWideChar(uint,ulong,char const *,int,ushort *,int)

I was collecting files with IsBad*Ptr in them, and have found plenty others including but not exclusively MSCOMCTL.OCX, EXCEL.EXE, Lenovo's, Corel's, Nokia's, AVerMedia's products...

UPDATE 13/May/2014 To add IsBad*Ptr to the program doesn't automatically mean to create bugs. However if IsBad*Ptr is present we have reason to believe that the function is expecting a pointer that might be invalid in certain circumstances. In that case IsBad*Ptr may be used to attack the program. And that's why it's important to conduct the audit according to this.

UPDATE 16/May/2014 The term "valid pointer" had an ambiguous meaning in the part Steps of Attacking. This is now reworded. Reference.

[1] IsBad*Ptr functions are IsBadReadPtr, IsBadCodePtr, IsBadWritePtr, and IsBadStringPtr. All of them are exports of kernel32.dll.

January 27, 2013

Entering into Out of Memory Condition

In this blog post I'm describing an approach to force the execution flow to enter into out of memory (OOM) error conditions when the amount of memory to allocate is not controlled by the attacker as in the example below.
#define MAX_SIZE 0x08000000 /* 128M */

ptr = malloc(MAX_SIZE)
if (ptr == NULL)
{
  return OUT_OF_MEMORY;
}
When testing software, it's reasonable to think that error conditions like above are unlikely to be reached as often as other error conditions. Some of them may never get reached because it's not obvious to make the allocation with fixed-size to fail.

OOM error condition could be a potential to enter into vulnerable code path. One example I've heard that the memory allocation had failed and the error condition led to call the cleanup functionality which caused to free of uninitialized pointer.

When testing, one possible way to make memory allocation to fail is to hook the memory allocation function and change the functionality to fail, that is to return with NULL pointer. However, I wanted to avoid this approach of testing because even if I find bug I'd still need a lot of work to reproduce it in real word scenario (if possible at all), when the hook is not applied.

Therefore, what I thought is that the other way to force fixed-size memory allocation to fail is to consume large amount of memory of the virtual address space, and as a consequence the program will be operating under low-memory conditions. This means if memory allocation occurs with reasonable small size it could fail because there is not much memory available. My theory is that any vulnerability found that we reach via OOM error conditions can be reproduced in a real word scenario because we always expect to find something that triggers to consume large amount memory in order to make other allocation to fail. This is pretty much possible in browsers where basically you can easily fill arbitrary amount of the heap memory.

Experimentally, I involved this approach in my fuzzing methodology. I attach the target process to Windbg and execute .dvalloc command to allocate arbitrary amount of memory in the virtual address space. The amount of memory available in the address space can be queried with !address -summary command, or can be seen in Process Explorer, and so you can allocate more for your test if needed.

When testing Firefox with this methodology I notice increased amount of int 3 crashes in mozalloc_abort() that is due to out of memory condition - those crashes are not exploitable though.

November 12, 2012

An Example for Hidden NULL Pointer Bug

When performing security testing of the native application I often attach Windbg to the target process. The obvious benefit of this methodology is that when an exception occurs it's possible to immediately analyze and save the state information of the program. There is one more important benefit, and that is you can get additional information of the running application if you set certain options beforehand. For example, sxn ud command sets up the debugger to send notification when a module is unloaded. I find very useful to analyze Windbg log during security testing, rather than just relying to find exception or memory corruption.

Some time ago, during testing Internet Explorer 9, I noticed that Windbg screen had been flooded with the following messages.
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
The application continued running without displaying more of these messages and there was no sign the integrity of the application corrupted. My first thought was it's the result of one of Windbg commands I set up earlier just before executing the test. The second thought was that it is unlikely because it seems to be a CRT error message, that most likely comes from the runtime DLL. I knew there is a way to send message to the debugger from an executable and that is to call OutputDebugString function.

I set up a breakpoint for OutputDebugString, and rerun the test. Here is the call stack when the breakpoint hit.
0:005> k
Child-SP          RetAddr           Call Site
00000000`04448388 000007fe`fdfd2478 KERNELBASE!OutputDebugStringA
00000000`04448390 000007fe`fdfd249b msvcrt!invoke_watson+0x98
00000000`044488d0 000007fe`fdfb0439 msvcrt!invalid_parameter+0x13
00000000`04448910 00000000`6027290f msvcrt!wcsnicmp+0x40
00000000`04448950 00000000`6000b522 MSHTML!CMultimediaLog::ExtractVideoData+0x8f
00000000`04448a30 00000000`600077ba MSHTML!CDoc::ExecHelper+0x4308
00000000`044498b0 000007fe`f1a22805 MSHTML!CDoc::Exec+0x2a
00000000`04449900 000007fe`f1a222f1 IEFRAME!CDocObjectHost::_PopulateOnlineHistoryData+0x141
00000000`04449990 000007fe`f1a21ecb IEFRAME!CDocObjectHost::_UpdateHistoryAndIntSiteDB+0x229
00000000`0444cb10 000007fe`f1a22001 IEFRAME!CDocObjectHost::_OnReadyState+0x21b
00000000`0444cda0 000007fe`f1a220bc IEFRAME!CDocObjectHost::_OnChangedReadyState+0xd1
00000000`0444ce70 00000000`5ff33c27 IEFRAME!CDocObjectHost::OnChanged+0x1c
00000000`0444cea0 00000000`5ff31380 MSHTML!CBase::FirePropertyNotify+0x3a3
00000000`0444cf30 00000000`5ff7f874 MSHTML!CMarkup::SetReadyState+0x41a
00000000`0444cfb0 00000000`5ff323b1 MSHTML!CMarkup::OnLoadStatusDone+0x3eb
00000000`0444d070 00000000`5ff7ef3f MSHTML!CMarkup::OnLoadStatus+0xb2
00000000`0444d0a0 00000000`5ff2f4e0 MSHTML!CProgSink::DoUpdate+0x5f5
00000000`0444d530 00000000`5fffb468 MSHTML!GlobalWndOnMethodCall+0x18b
00000000`0444d5c0 00000000`76de9bd1 MSHTML!GlobalWndProc+0x36c
00000000`0444d640 00000000`76de98da USER32!UserCallWinProcCheckWow+0x1ad
00000000`0444d700 000007fe`f198af5e USER32!DispatchMessageWorker+0x3b5
00000000`0444d780 000007fe`f1937754 IEFRAME!CTabWindow::_TabWindowThreadProc+0x9c1
00000000`0444fbe0 00000000`771553b3 IEFRAME!LCIETab_ThreadProc+0x39f
00000000`0444fd80 000007fe`f1918dcb iertutil!CIsoScope::RegisterThread+0x10f
00000000`0444fdb0 00000000`76ee652d IEFRAME!Detour_DefWindowProcA+0x97
00000000`0444fdf0 00000000`774dc521 kernel32!BaseThreadInitThunk+0xd
00000000`0444fe20 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
Looking at the parameter of OutputDebugString I saw the followings.
0:005> db @rcx
000007fe`fe014110  49 6e 76 61 6c 69 64 20-70 61 72 61 6d 65 74 65  Invalid paramete
000007fe`fe014120  72 20 70 61 73 73 65 64-20 74 6f 20 43 20 72 75  r passed to C ru
000007fe`fe014130  6e 74 69 6d 65 20 66 75-6e 63 74 69 6f 6e 2e 0a  ntime function..
000007fe`fe014140  00 00 00 00 3a 20 00 00-0a 00 0a 00 00 00 00 00  ....: ..........
000007fe`fe014150  2e 00 2e 00 2e 00 00 00-00 00 00 00 00 00 00 00  ................
000007fe`fe014160  4d 00 69 00 63 00 72 00-6f 00 73 00 6f 00 66 00  M.i.c.r.o.s.o.f.
000007fe`fe014170  74 00 20 00 56 00 69 00-73 00 75 00 61 00 6c 00  t. .V.i.s.u.a.l.
000007fe`fe014180  20 00 43 00 2b 00 2b 00-20 00 52 00 75 00 6e 00   .C.+.+. .R.u.n.
This confirms this function sends the message to the debugger. We also see that OutputDebugString is called from wcsnicmp that is called from CMultimediaLog::ExtractVideoData.

I set up a breakpoint just before we call wcsnicmp.
0:005> ub MSHTML!CMultimediaLog::ExtractVideoData+0x8f
MSHTML!CMultimediaLog::ExtractVideoData+0x69:
00000000`602728e9 7409            je      MSHTML!CMultimediaLog::ExtractVideoData+0x74 (00000000`602728f4)
00000000`602728eb 488b01          mov     rax,qword ptr [rcx]
00000000`602728ee ff9020050000    call    qword ptr [rax+520h]
00000000`602728f4 4c8b6c2428      mov     r13,qword ptr [rsp+28h]
00000000`602728f9 488d15480f6900  lea     rdx,[MSHTML!`string' (00000000`60903848)]
00000000`60272900 41b807000000    mov     r8d,7
00000000`60272906 498bcd          mov     rcx,r13
00000000`60272909 ff1591ca5e00    call    qword ptr [MSHTML!_imp__wcsnicmp (00000000`6085f3a0)]
The place of the breakpoint is the highlighted line above.

Here we are when the breakpoint is hit.
Breakpoint 2 hit
MSHTML!CMultimediaLog::ExtractVideoData+0x89:
00000000`60272909 ff1591ca5e00    call    qword ptr [MSHTML!_imp__wcsnicmp (00000000`6085f3a0)] ds:00000000`6085f3a0={msvcrt!wcsnicmp (000007fe`fdf93330)}
Note that wcsnicmp takes three parameters: string1, string2, and count. These describes null-terminated strings and number of characters to compare.

I checked the parameters of wcsnicmp and saw the followings.
0:005> r rcx;r rdx;r r8
rcx=0000000000000000
rdx=0000000060903848
r8=0000000000000007
string2 and count both look healthy however string1 is NULL but the API documentation expects a string.

To make sure everything is ok with string2 let's check.
0:005> du @rdx
00000000`60903848  "http://"
It's fine, so the only problem with parameter string1 and the problem is that a NULL pointer is passed rather than a pointer to the string.

Microsoft long time ago added security enhancements in the CRT library. One of them involves to call invalid parameter handler which, for example, checks for NULL pointer parameters, and we terminate gracefully rather than with invalid memory access exception.

As you can see the return value is the highest positive value that can be represented in signed 32-bit. If the application is not prepared to handle this edge value it could cause problem later on the execution. Fortunately, that was not the case with Internet Explorer.
0:005> g 6027290f
MSHTML!CMultimediaLog::ExtractVideoData+0x8f:
00000000`6027290f baffff0000      mov     edx,0FFFFh
0:005> r eax
eax=7fffffff
The NULL check suppresses the crash but the application likely having problems managing the lifetime of objects.

Developers are advised to avoid the use of wcsnicmp() function as it's deprecated. Please check references what to use instead.

References

Security Features in the CRT

April 28, 2012

Testing Firefox and Chrome on fuzzed SVG files

Fuzzer: My first SVG fuzzer (called SVGz)
Options: Switch two randomly selected tags (which is the only one mode at the moment)
Number of template files: 659
Size of the smallest template file: 140 byte
Size of the largest template file: 32M
Number of mutated files to test with: 222,888

Firefox: One crash - Bug 747661
Chrome: Some crashes with similar state - Issue 124585

Neither of crashes are exploitable for code execution.

The SVG files were processed in the same browser instance until crash occurred, to save some time that bootstrap and shutdown would require. Firefox and Chrome were tested simultaneously, and the test took about 4 days to complete.
  This blog is written and maintained by Attila Suszter. Read in Feed Reader.