:: 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
http://www.microsoft.com/
:: 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:
.net\coreclr\src\mscorlib\src\System\Resources\ResourceReader.cs
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)
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() {
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 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]
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:
>!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);
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 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 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 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 -> 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 mscorlib_ni+0x348b26: 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 mscorlib_ni+0x348b29: 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 mscorlib_ni+0x348a9c: 795a8a9c 0fb601 movzx eax,byte ptr [ecx] ds:002b:80be126c=??
:: Crash Information
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 ---------
:: References
[1] http://www.codeproject.com/Articles/12096/NET-Manifest-Resources
[2] https://github.com/dotnet/coreclr