(English) Microsoft .NET/Silverlight Manifest Resource Information Disclosure Vulnerability [CVE-2015-6114 TALOS-CAN-0130]

:: Description

An exploitable information leak or denial of service vulnerability exists in the manifest resource parsing functionality of the .NET Framework. A specially crafted resource can cause an integer overflow resulting in an out of bounds read which may return arbitrary memory contents or cause the application to unexpectedly terminate. An attacker can supply a corrupted manifest through a .NET or Silverlight assembly, which can be automatically loaded in Internet Explorer or other browsers with the appropriate plugin installed. This vulnerability can be used to crash the program or to return memory contents to assist with bypassing memory hardening mitigations such as ASLR.

:: Tested Versions

Microsoft Silverlight 5.1.30514.0
.NET Framework 4.5.50938

:: Product URLs


:: CVSSv3 Score

6.1 – CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H

:: Details

.NET Manifest Resources format is undocumented but to learn about it we use one of source mentioned in references [1][2].
It’s structure more less looks in the following way:

+00 : Signature | needs to equal 0xbeefcace
+04 : Resource Manager header version
+08 : Reader Type string length [*]
+0C : Reader String |
[*]   : Resource version
[*]+4 : Number of resources
[*]+8 : Number of types
[ types ]
[align 8]
[Resources Name hashes]         = Number of resource * DWORD
[Offsets to resource names]     = Number of resource * DWORD
[..]  : Data Section offset

We will focus our attention on Number of resources field.
Number of resources in my test application is equal 1, but I malformed its value to :
0x40000001 let’s see what i cause.
Vulnerable code appears in ResourceReader class:


which has couple constructors but in our scenario we gonna audit one of path where called constructor assign value to _ums field.
So it can be :

Line 175        public ResourceReader(Stream stream)


Line 197 internal ResourceReader(Stream stream, Dictionary<String, ResourceLocator> resCache)

Let’s we analyze the way .NET Manifest Resources structure is parsed.
Both constructors call internal _ReadResources method:

Line 870    private void _ReadResources()

where appropriate parsing has place.

933  _numResources = _store.ReadInt32();
934  if (_numResources < 0) {
935      throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted"));
936  }

Number of resources is represented by 32bit signed integer variable which value can’t be larger than MAX_INT.
Later _numResources is used two times in code to calculate stream offset:

991  else {
992      int seekPos = unchecked(4 * _numResources);
993      if (seekPos < 0) {
994          throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted"));
995      }
996      unsafe {
997          _nameHashesPtr = (int*)_ums.PositionPointer;
998          // Skip over the array of nameHashes.
999          _ums.Seek(seekPos, SeekOrigin.Current);
1000         // get the position pointer once more to check that the whole table is within the stream
1001         byte* junk = _ums.PositionPointer;
1002     }
1003 }

You can notice already vulnerable code at line 992 where integer overflow appears. But we won’t focus on it right now, just remember that _nameHashesPtr pointer was initialized here and it points on area where Resources Name hashes(DWORDs) should appears.
We can observe here also that programmer planned to check whether pointer is assigned in stream boundaries but finally nothing is checked.
Second usage of _numResources:

	else {
		int seekPos = unchecked(4 * _numResources);
		if (seekPos < 0) {
			throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted"));
		unsafe {
			_namePositionsPtr = (int*)_ums.PositionPointer;
			// Skip over the array of namePositions.
			_ums.Seek(seekPos, SeekOrigin.Current);
			// get the position pointer once more to check that the whole table is within the stream
			byte* junk = _ums.PositionPointer;

Another integer overflow and lack of checking whether _namePositionsPtr points out-of-range.
As we know already where and how _numResources,_nameHashesPtr is set and that there is nearly lack of sanitization for these variables we can run test application with malformed .NET res manifest and see where it crashes.
If application has one .NET resource and it’s major resource e.g MainPage.xaml it gonna be
automatically loaded during application start in components initialization method:

47  [System.Diagnostics.DebuggerNonUserCodeAttribute()]
48   public void InitializeComponent() {
49      if (_contentLoaded) {
50          return;
51      }
52      _contentLoaded = true;
53      System.Windows.Application.LoadComponent(this, new System.Uri("/SilverlightApplication1;component/MainPage.xaml", System.UriKind.Relative));

which gonna trigger vulnerability.
Let’s wee see where application crash:

:::: Crash Analysis

1:035> r
eax=40000000 ebx=20000000 ecx=80be126c edx=20000000 esi=00000000 edi=055b5318
eip=795a8a9c esp=0037ebe8 ebp=0037ebec iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210200
795a8a9c 0fb601          movzx   eax,byte ptr [ecx]         ds:002b:80be126c=??
1:035> !u eip
preJIT generated code
Begin 795a8a9c, size 1f
>>> 795a8a9c 0fb601          movzx   eax,byte ptr [ecx]
795a8a9f 0fb65101        movzx   edx,byte ptr [ecx+1]

Ok, access violation appears in ReadUnalignedI4 method which code presents in the following way:

245  [System.Security.SecurityCritical]  // auto-generated
246  internal static unsafe int ReadUnalignedI4(int* p)
247  {
248      byte* buffer = (byte*)p;
249      // Unaligned, little endian format
250      return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
251  }

To understand from this *p pointer came from and how its value was calculated let’s we take a look on call stack:

System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Resources.ResourceLocator, mscorlib]].TryGetValue(System.__Canon, System.Resources.ResourceLocator ByRef)),
System.Resources.RuntimeResourceSet.GetObject(System.String, Boolean, Boolean)),
System.Resources.RuntimeResourceSet.GetObject(System.String, Boolean)),
System.Resources.ResourceManager.GetObject(System.String, System.Globalization.CultureInfo, Boolean))
System.Resources.ResourceManager.GetStream(System.String, System.Globalization.CultureInfo)),
System.Windows.ResourceManagerWrapper.GetResourceForUri(System.Uri, System.Type)),
System.Windows.Application.LoadComponent(System.Object, System.Uri)),

We will start our investigation in FindPosForResource method.

316  internal int FindPosForResource(String name)
317  {
318      Contract.Assert(_store != null, "ResourceReader is closed!");
319      int hash = FastResourceComparer.HashFunction(name);
320      BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for "+name+"  hash: "+hash.ToString("x", CultureInfo.InvariantCulture));
321      // Binary search over the hashes.  Use the _namePositions array to
322      // determine where they exist in the underlying stream.
323      int lo = 0;
324      int hi = _numResources - 1;
325      int index = -1;
326      bool success = false;
327      while (lo <= hi) {
328          index = (lo + hi) >> 1;
329          // Do NOT use subtraction here, since it will wrap for large
330          // negative numbers.
331          int currentHash = GetNameHash(index);

As You can guess function is searching for particular resource pointed by name using its hash representation Line 319. Calculated hash is search in array pointed by _nameHashesPtr which initialization we observe in _ReadResources method. Index for this array is calculated based on malformed by us _numResources. Let we remind that original value of _numResources was 1 and malformed is 0x40000001. Having breakpoint at the beginning of FindPosForResource method we can easy check it out:

eax=055b52c0 ebx=055b2e0c ecx=055b5318 edx=055b3938 esi=ffffffff edi=0037ec88
eip=795a8c3c esp=0037ec3c ebp=0037ec98 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
795a8c3c 55              push    ebp
1:035> !dumpobj ecx
Name:        System.Resources.ResourceReader
MethodTable: 796f446c
EEClass:     792a6fd4
Size:        68(0x44) bytes
File:        C:\Program Files (x86)\Microsoft Silverlight\5.1.30514.0\mscorlib.dll
      MT    Field   Offset                 Type VT     Attr    Value Name
796f40c0  4000f6e       14 ...m.IO.BinaryReader  0 instance 055b535c _store
796f42cc  4000f6f       18 ...cator, mscorlib]]  0 instance 055b52e4 _resCache
796e737c  4000f70        4         System.Int64  1 instance 188 _nameSectionOffset
796e737c  4000f71        c         System.Int64  1 instance 211 _dataSectionOffset
796cf828  4000f72       1c       System.Int32[]  0 instance 00000000 _nameHashes
796f8ae8  4000f73       30                  PTR  0 instance 00be126c _nameHashesPtr
796cf828  4000f74       20       System.Int32[]  0 instance 00000000 _namePositions
796f8ae8  4000f75       34                  PTR  0 instance 00be1270 _namePositionsPtr
796d17d0  4000f76       24      System.Object[]  0 instance 055b5850 _typeTable
796cf828  4000f77       28       System.Int32[]  0 instance 055b5860 _typeNamePositions
796e71d4  4000f78       38         System.Int32  1 instance 1073741825 _numResources
796ee0d4  4000f79       2c ...nagedMemoryStream  0 instance 055b4d04 _ums
796e71d4  4000f7a       3c         System.Int32  1 instance        2 _version

First index value calculated will be 0x20000000. Further index is passed to GetNameHash function.

266  [System.Security.SecuritySafeCritical]  // auto-generated
267  private unsafe int GetNameHash(int index)
268  {
269      Contract.Assert(index >=0 && index < _numResources, "Bad index into hash array.  index: "+index);
270      Contract.Assert((_ums == null && _nameHashes != null && _nameHashesPtr == null) ||
271                      (_ums != null && _nameHashes == null && _nameHashesPtr != null), "Internal state mangled.");
272      if (_ums == null)
273          return _nameHashes[index];
274      else
275          return ReadUnalignedI4(&_nameHashesPtr[index]);
276  }

According to call stack and _ums value which we can observe on ResourceRader object dump above another call is made to ReadUnalignedI4 method where crash appears. Pointer passed to this method as argument is just pointer to element from _nameHashesPtr table. As we can see index argument is used as index in name hashes array which in this case as You can guess will point much much more outside array (Resource section/directory) space.

1:035> !dumpobj 055b4d04  //dump of _ums object
Name:        System.IO.UnmanagedMemoryStream
MethodTable: 796ee0d4
EEClass:     792a39ac
Size:        56(0x38) bytes
File:        C:\Program Files (x86)\Microsoft Silverlight\5.1.30514.0\mscorlib.dll
      MT    Field   Offset                 Type VT     Attr    Value Name
796f2674  400123d      3c0     System.IO.Stream  0   shared   static Null
    >> Domain:Value  0500a6b0:NotInit  050acb70:NotInit  <<
796efb84  4001320       24 ...rvices.SafeBuffer  0 instance 00000000 _buffer
796f8ae8  4001321       28                  PTR  0 instance 00be11bc _mem
796e737c  4001322        4         System.Int64  1 instance 1954 _length
796e737c  4001323        c         System.Int64  1 instance 1954 _capacity
796e737c  4001324       14         System.Int64  1 instance 188 _position
796e737c  4001325       1c         System.Int64  1 instance 0 _offset
796cf860  4001326       2c         System.Int32  1 instance        1 _access
796e6b40  4001327       30       System.Boolean  1 instance        1 _isOpen

00be11bc _mem -> pointer to the beginning of .Net Resources Manifest
1954 _length
, so definitely _nameHashesPtr should be constrained to this area.
Moment when pointer to hash element is calculated:

eax=40000000 ebx=20000000 ecx=00be126c edx=20000000 esi=00000000 edi=055b5318
eip=795a8b26 esp=0037ebec ebp=0037ebec iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
795a8b26 8d0c91          lea     ecx,[ecx+edx*4]

notice that index is multiply by 4 (sizeof of element in array). Another place for overflow.

1:035> p
eax=40000000 ebx=20000000 ecx=80be126c edx=20000000 esi=00000000 edi=055b5318
eip=795a8b29 esp=0037ebec ebp=0037ebec iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200202
795a8b29 e86effffff      call    mscorlib_ni+0x348a9c (795a8a9c)

and finally read access violation appears during read of first byte from calculated pointer:

1:035> p
(23e8.2504): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=40000000 ebx=20000000 ecx=80be126c edx=20000000 esi=00000000 edi=055b5318
eip=795a8a9c esp=0037ebe8 ebp=0037ebec iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210202
795a8a9c 0fb601          movzx   eax,byte ptr [ecx]         ds:002b:80be126c=??

:: Crash Information

795a8a9c 0fb601          movzx   eax,byte ptr [ecx]
EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 795a8a9c (mscorlib_ni+0x00348a9c)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: 80be126c
Attempt to read from address 80be126c
PROCESS_NAME:  plugin-container.exe
ERROR_CODE: (NTSTATUS) 0xc0000005 - Instrukcja spod 0x%08lx odwo
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - Instrukcja spod 0x%08lx odwo
READ_ADDRESS:  80be126c
795a8a9c 0fb601          movzx   eax,byte ptr [ecx]
APP:  plugin-container.exe
0037EBE8 795A8A9C mscorlib_ni!System.Resources.ResourceReader.ReadUnalignedI4(Int32*)
0037EBEC 795A8B2E mscorlib_ni!System.Resources.ResourceReader.GetNameHash(Int32)+0x22
0037EBF4 795A8C88 mscorlib_ni!System.Resources.ResourceReader.FindPosForResource(System.String)+0x4c
0037EC40 795A6774 mscorlib_ni!System.Resources.RuntimeResourceSet.GetObject(System.String, Boolean, Boolean)+0xb8
0037ECA8 795A646D mscorlib_ni!System.Resources.RuntimeResourceSet.GetObject(System.String, Boolean)+0xd
0037ECB0 795A3057 mscorlib_ni!System.Resources.ResourceManager.GetObject(System.String, System.Globalization.CultureInfo, Boolean)+0xc7
0037ECF4 795A3163 mscorlib_ni!System.Resources.ResourceManager.GetStream(System.String, System.Globalization.CultureInfo)+0x13
0037ED08 56E28AB7 System_Windows_ni!System.Windows.ResourceManagerWrapper.GetResourceForUri(System.Uri, System.Type)+0x197
0037ED4C 56E0604A System_Windows_ni!System.Windows.Application.LoadComponent(System.Object, System.Uri)+0x17a
0037ED90 00BF02C6 UNKNOWN!Inplay.Page.InitializeComponent()+0x8e
0037EDD8 00BF0217 UNKNOWN!Inplay.Page..ctor(System.Collections.Generic.IDictionary`2<System.String,System.String>)+0xc7
0037EDEC 00BF0126 UNKNOWN!Inplay.Application.Application_Startup(System.Object, System.Windows.StartupEventArgs)+0x5e
0037EE0C 56E27053 System_Windows_ni!MS.Internal.CoreInvokeHandler.InvokeEventHandler(UInt32, System.Delegate, System.Object, System.Object)+0x3d3
0037EE38 56E052A9 System_Windows_ni!MS.Internal.JoltHelper.FireEvent(IntPtr, IntPtr, Int32, Int32, System.String, UInt32)+0x38d
0037EE88 56EA0A59 System_Windows_ni!DomainNeutralILStubClass.IL_STUB_ReversePInvoke(Int32, Int32, Int32, Int32, IntPtr, Int32)+0x61
LAST_CONTROL_TRANSFER:  from 795a8c88 to 795a8a9c
0037ebe8 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ebec 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ebf4 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ec40 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037eca8 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ecb0 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ecf4 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ed08 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ed4c 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037ed90 00000000 unknown.dll!Inplay.Page.InitializeComponent+0x8e
0037edd8 00000000 unknown.dll!Inplay.Page..ctor+0xc7
0037edec 00000000 unknown.dll!Inplay.Application.Application_Startup+0x5e
0037ee0c 56e27053 system_windows_ni!MS.Internal.CoreInvokeHandler.InvokeEventHandler+0x3d3
0037ee38 56e052a9 system_windows_ni!MS.Internal.JoltHelper.FireEvent+0x38d
0037ee88 56ea0a59 system_windows_ni!DomainNeutralILStubClass.IL_STUB_ReversePInvoke+0x61
SYMBOL_NAME:  unknown.dll!Inplay.Page.InitializeComponent
FOLLOWUP_NAME:  MachineOwner
MODULE_NAME: unknown
IMAGE_NAME:  unknown.dll
STACK_COMMAND:  _EFN_StackTrace ; ** Pseudo Context ** ; kb
FAILURE_BUCKET_ID:  WRONG_SYMBOLS_c0000005_unknown.dll!Inplay.Page.InitializeComponent
BUCKET_ID:  APPLICATION_FAULT_WRONG_SYMBOLS_unknown.dll!Inplay.Page.InitializeComponent
Followup: MachineOwner

:: References

[1] http://www.codeproject.com/Articles/12096/NET-Manifest-Resources
[2] https://github.com/dotnet/coreclr

This entry was posted in Analiza, Bez kategorii, Bugs, Security and tagged , , , , , . Bookmark the permalink.