The blog continues at suszter.com/ReversingOnWindows

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.

August 23, 2015

Fully Managed C# Can Corrupt Memory

In this post I'm writing about memory corruption in fully managed C# code. On the internet, many people advocate that managed C# should be immune against memory corruption. Even reputable organizations mention the same but provide no reference so people can't check the source of the information for themselves.

The fact is, Microsoft don't explicitly claim whether or not managed C# should introduce memory corruption in your app.

Input Sanitization

The materials made by Microsoft mentions the importance of sanitizing input values in managed code. This also means that things can go wrong if you just pass untrusted values to framework APIs.

AccessViolationException

.NET Framework has the AccessViolationException exception class for accessing protected memory. The description says:
An access violation occurs in unmanaged or unsafe code when it attempts to read or write to memory that has not been allocated, or to which it does not have access.
This is quite true, however, AccessViolationException can be originated from fully managed code, too.

In the past months, I was doing some C# development, and during the testing, I experienced several times that my app had terminated with the sign of memory corruption:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
It looks this error is commonly seen in the search result.

Memory Corruption

I did a bit of research on this topic, and wrote a simplified testcase for one of such corruption. I sent it to the people at MSRC. When they finished their investigation I received a reply from them:
We have successfully reproduced the issue and can confirm that the memory corruption is happening within the application itself and not the system.  As such, this does not appear to be a security issue and I will be closing this case.
The statement above is a bit vague but this is my understanding of what it means.

If there is a C# API that's designed to handle untrusted data (such as a decompression API) and there is a memory corruption bug in the way the API handles the data the bug may be a security issue, and so it has to be fixed by Microsoft.

However, if the bug is triggered due to an erroneous use of the C# API, such as a non-sanitized parameter is supplied, then the bug is considered to be fixed in the application.

Whether the bug is in the system or application you can end up corrupting the memory in the process so it can affect the security of your app. Despite this the bug may not be qualified for a fix in the .NET framework.

.NET framework manages the memory to eliminate the memory problems but this doesn't guarantee that it eliminates all the memory problems. Memory corruption in fully managed C# is possible.

Testcase

The testcase is a Visual Studio 2013 project that reproduces the memory corruption on Windows Phone 8.1 device and Windows Phone 8.1 Emulator.

The testcase creates an array of pixels with the use of IRandomAccessStream, BitmapEncoder and Stream. When checksumming the bytes in the array the checksum changes every time the vulnerability is triggered.

Checksumming is simple like this (the idea is borrowed from one of the public Flash infoleak made by Chris Evans).

            int checksum = 0;
            foreach (byte i in arr)
            {
                checksum += arr[i];
            }

Sometimes the testcase crashes with AccessViolationException but most of the time it's observed that the app continues to run after the memory corruption.

February 24, 2015

Simple Code Coverage Analyzer

coco.cpp is a simple pintool for code coverage analysis. It comes with the Pin Framework.

The reason I write a post about it is because it's really a simple but well-designed tool. The code coverage information for an executable section is stored in a vector of booleans. Each boolean represents a byte in the executable section. If the boolean is set true it means the corresponding byte has been executed. If the boolean is set false the corresponding byte is untouched.

If you want the tool to produce the code coverage hash just add yourself a call that calculates the hash of the vector of booleans. This works well when the pintool is executed on small programs.

However when working with more complex programs it's possible you notice that different code coverage hashes produced for seemingly similar executions. This is not an error. The code coverage can be different between the executions in the finest graduality sense. One example is when the application exits via different path between the executions.

To filter these differences above you can virtually split the vector into many regions and produce the hash of each region. Now you may see that most of the hashes are the same between the executions.

UPDATE 24/February/2015 No official PIN repository to see coco.cpp but Gunther was kind to share it via Twitter for those want to take a look at without the need to download the framework. Thx!

January 18, 2015

Even Calculator Has Bugs

Windows Calculator (calc.exe) has a functional bug that is associated with the clipboard and the integers below.

INT8_MIN  -128
INT16_MIN -32768
INT32_MIN -2147483648
INT64_MIN -9223372036854775808


In Programmer mode (View -> Programmer) when Dec and Byte are selected the minimum value the calculator can accept is -128. It's tricky to enter that value but you can do by the following four key-presses: (1) (+/-) (2) (8). I said it's tricky as you can't type (1) (2) (8) (+/-) because 128 is greater than the maximum value of INT8 that is 127. While it's a bit inconvenience it can be worked around.

To reproduce the bug select Dec and Byte and type -128. Copy it to the clipboard via Edit -> Copy or Ctrl+C. Then paste it via Edit->Paste or Ctrl+V. What happens is -12 is pasted rather than -128, discarding the last digit.
Last digit is discarded when -128 (INT8_MIN) is pasted.

The effect is the same if you copy -128 in Notepad then paste it in Calculator.

While a beeping sound may be heard when pasting, nothing else warns you about the error. Those who muted the speaker may not notice the error. This is especially possible when the value is long in digits (INT64_MIN) and can be easily overlooked.

It's plausible that programmers use INT_MIN values in their calculations as these are commonly used when developing software.

Windows 8.1 is affected by this issue. Other versions are not verified but likely affected.

While this issue is most likely the limitation of the design some may find it embarrassing as a calculator has nothing else to do than to work with numbers without failure.

The issue found when I was recently working on my own calculator for Windows Phone. I realized I had to deal with many corner cases and I checked how these corner cases affect Windows Calculator.

January 11, 2015

Arrangement Of The Variables In Data Structures

Let's assume that an extensive code review had identified the direct security issues and the developers eliminated all. Now the code is free from bugs. How nice to write that. :-)

That would not be the reason to stop code review though. There may be still things to be done to reduce the severity of potential future bugs.

Newly added code can introduce and/or expose issues. The idea is to make changes in the existing code that make the exploitation of a future bug more challenging. This is good to do without introducing new issues like performance degradation, etc.

Given a heap based buffer overflow, one of the common attack scenario is to overwrite a function pointer in any structure. If, however, the function pointer is at the end of the structure, the overflow may:
  • Not be long enough to overwrite the function pointer
  • Overwrite the variables preceding the function pointer and so destroy the internal state of the application. This could lead the application to bail out early before the function gets called.
So putting the function pointer as a last item in the structure can make exploitation more challenging.

A more generic suggestion in security point of view is like this. If possible, arrange the variables to have
  • the sensitive ones at the end of the structure
  • the ones that can bail the execution out early at the beginning of the structure

January 6, 2015

EMET Can Interfere With Pin

Sometimes no matter how simple your pintool is when it's run against the target it crashes. Recently I investigated such crash. One of my pintool was randomly crashed/hung/terminated as well as displayed memory errors.

My target was added to EMET (Enhanced Mitigation Experience Toolkit). Also, EMET was running at the time of experiment. I was thinking... what if I stop EMET? I disabled all the mitigations in EMET and stopped its service. Re-launched my pintool and checked if EMET.dll and EMET64.dll are not loaded in the target's process. This time my pintool was running without any obstacle.

Third party programs can make a pintool to crash. Of course, it's not a surprise but it's something to be aware of when working with a pintool.

If your pintool is still crashing you may want to take look at one of my earlier post.
  This blog is written and maintained by Attila Suszter. Read in Feed Reader.