CVE-2015-6114
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.
Microsoft Silverlight 5.1.30514.0
Microsoft .NET Framework 4.5.50938
6.1 - CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H
The .NET Manifest Resources format is undocumented but to learn about it we use one of source mentioned in references [1] [2]. The resource structure is documented 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. An attacker can modify this field to corrupt a pointer in the following way.
Vulnerable code appears in ResourceReader class: .net\coreclr\src\mscorlib\src\System\Resources\ResourceReader.cs This class has multipple constructors, in our scenario we are interested in constructors that assign a value to the _ums field. So it can be :
Line 175 public ResourceReader(Stream stream)
or
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()
{
(...)
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 see the 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 to area where Resources Name hashes(DWORDs) should appears. We observe here also that the programmer planned to check whether pointer is assigned within stream boundaries but ultimately 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;
}
}
This code containts another integer overflow and lack of checking whether _namePositionsPtr points out-of-bounds. As we know already where and how _numResources and _nameHashesPtr is set and that there is lack of sanitization for these variables, we can run test application with malformed .NET resource manifest and see where it crashes.
If application has one .NET resource and it’s major resource e.g MainPage.xaml it will be automatically loaded during application start in components initialization method which will trigger the vulnerability:
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));
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
mscorlib_ni+0x348a9c:
795a8a9c 0fb601 movzx eax,byte ptr [ecx] ds:002b:80be126c=??
1:035> !u eip
preJIT generated code
System.Resources.ResourceReader.ReadUnalignedI4(Int32*)
Begin 795a8a9c, size 1f
>>> 795a8a9c 0fb601 movzx eax,byte ptr [ecx]
795a8a9f 0fb65101 movzx edx,byte ptr [ecx+1]
Access violation appears in ReadUnalignedI4 method 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 where this *p pointer came from and how its value was calculated let’s we take a look at the call stack:
>!dumpstack
System.Resources.ResourceReader.ReadUnalignedI4(Int32*))
System.Resources.ResourceReader.GetNameHash(Int32)),
System.Resources.ResourceReader.FindPosForResource(System.String))
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)),
SilverlightApp1.MainPage.InitializeComponent()),
(...)
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);
This function is searching for a particular resource name using its hash representation on Line 319. Calculated hash is searched for in the array pointed by _nameHashesPtr which is initialized in the _ReadResources method. Index for this array is calculated based on malformed by us _numResources. In this example we modified the original value of _numResources from 1 to 0x40000001 in the manifest. Setting a 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
mscorlib_ni+0x348c3c:
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
Fields:
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 into the name hashes array. In this case the corrupted value will point 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
Fields:
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 is a pointer to the beginning of .Net Resources Manifest and the _nameHashesPtr should be constrained within the _mem + _length memory boundary.
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
mscorlib_ni+0x348b26:
795a8b26 8d0c91 lea ecx,[ecx+edx*4]
Notice that here the index is multiplied by 4 (sizeof of element in array) which is another place for possible 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
mscorlib_ni+0x348b29:
795a8b29 e86effffff call mscorlib_ni+0x348a9c (795a8a9c)
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
mscorlib_ni+0x348a9c:
795a8a9c 0fb601 movzx eax,byte ptr [ecx] ds:002b:80be126c=??
FAULTING_IP:
mscorlib_ni+348a9c
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
FAULTING_THREAD: 00002504
DEFAULT_BUCKET_ID: WRONG_SYMBOLS
PROCESS_NAME: plugin-container.exe
ERROR_CODE: (NTSTATUS) 0xc0000005 - Instrukcja spod 0x%08lx odwo
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - Instrukcja spod 0x%08lx odwo
EXCEPTION_PARAMETER1: 00000000
EXCEPTION_PARAMETER2: 80be126c
READ_ADDRESS: 80be126c
FOLLOWUP_IP:
mscorlib_ni+348a9c
795a8a9c 0fb601 movzx eax,byte ptr [ecx]
NTGLOBALFLAG: 470
APPLICATION_VERIFIER_FLAGS: 0
APP: plugin-container.exe
MANAGED_STACK:
(TransitionMU)
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
(TransitionUM)
MANAGED_STACK_COMMAND: _EFN_StackTrace
LAST_CONTROL_TRANSFER: from 795a8c88 to 795a8a9c
PRIMARY_PROBLEM_CLASS: WRONG_SYMBOLS
BUGCHECK_STR: APPLICATION_FAULT_WRONG_SYMBOLS
STACK_TEXT:
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_STACK_INDEX: 0
SYMBOL_NAME: unknown.dll!Inplay.Page.InitializeComponent
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: unknown
IMAGE_NAME: unknown.dll
DEBUG_FLR_IMAGE_TIMESTAMP: 0
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 ---------
[1] http://www.codeproject.com/Articles/12096/NET-Manifest-Resources
[2] https://github.com/dotnet/coreclr
2015-05-08 - Vendor Disclosure
2015-12-08 - Public Disclosure
Marcin ‘Icewall’ Noga of Cisco Talos